Accessing child constants from base class

php.internals

Dan Cox

22 years ago
PHP5 10/10/2003 CVS. Currently, I don't see an easy way of accessing the constants of a child class from a base class. Consider the following basic example: abstract class myBaseClass { function getXML() { $doc = new domDocument(); $node = $doc->createElement(child::ElementName); $doc->appendChild($node); return $doc->saveXML($node); } } class myChildClass extends myBaseClass { const ElementName = 'foo'; // ..lots of methods, etc here.. } $foo = new myChildClass(); print($foo->getXML()); Of course, this does not work as there is no 'child::' accessor. The alternatives to making this work are: Use a protected variable in the child (and, of course, don't forget to also say 'protected $ElementName;' in the base class) and use $this->ElementName for accessing the child's ElementName from the parent. This is probably good enough, but much less than ideal. Use this beautifully low maintenance, high performance piece of code in the base class (not): $className = get_class($this); switch($className) { case 'mychildclass': $childElementName = myChildClass::ElementName; break; case 'myotherchildclass': $childElementName = myOtherChildClass::ElementName; break; // etc..... } .... ick! You may be able to shortcut the need for the switch statement with some variable variable and/or eval() trickery, but let's not even go there.. So are there any plans on implementing child:: ? Is there a way of accessing child constants without having to explicitly known the current and/or child class name? Any input appreciated- Dan Cox

Marcus Börger

22 years ago
Hello Dan, Friday, October 10, 2003, 9:10:23 AM, you wrote:
> PHP5 10/10/2003 CVS.
> Currently, I don't see an easy way of accessing the constants of a child > class from a base class.
Use the child's class name: php -r 'class t{static function f(){echo tt::c;}}class tt extends t{const c="Hello\n";} t::f();' But i guess you want something dynamically as $class::const, right? the next example uses 'self' which doesn't work because self is bound at compile time: php -r 'class t{function t(){echo self::c;}}class tt extends t{const c= "Hello\n";} $o=new tt;' So you'd need something like '$this::c' which is impossible right now. Funny thing is the following which returns NULL what makes absolute no sense to me. It somehow looks like either the correct constant is used but it is uninitialized or there is an error missing. php -r 'class t{function t(){var_dump($this->c);}}class tt extends t{const c= "Hello\n";} $o=new tt;'
-- Best regards, Marcus mailto:helly@php.net

Dan Cox

22 years ago
Marcus- Marcus Börger wrote:
> Hello Dan, > > >> Currently, I don't see an easy way of accessing the constants of a child >> class from a base class. >> > > Use the child's class name: > php -r 'class t{static function f(){echo tt::c;}}class tt extends > t{const c="Hello\n";} t::f();' > > >
Yes, this is possible, but it assumes you always only ever have one child class, which the base class knows the name of (unlikely). You could figure things out dynamically, as I provided some example code for, but this would become unwieldy with 10s to 100s of derived classes.
> But i guess you want something dynamically as $class::const, right? > > >
right.
> the next example uses 'self' which doesn't work because self is bound at > compile time: > php -r 'class t{function t(){echo self::c;}}class tt extends t{const > c= "Hello\n";} $o=new tt;' > > So you'd need something like '$this::c' which is impossible right now. > > Funny thing is the following which returns NULL what makes absolute no > sense > to me. It somehow looks like either the correct constant is used but > it is > uninitialized or there is an error missing. > php -r 'class t{function t(){var_dump($this->c);}}class tt extends > t{const c= "Hello\n";} $o=new tt;' > > >
I believe this is correct behaviour. When instantiating an object the methods, properties, etc. of the child class and any and all parents are in essence aggregated together into a new object (the instance). It would make sense (at least to me) that 'class constants' should NOT be aggregated together on instantiation. They should be constant only for their particular class definition and all code provided by that class. So you could have a const foo = 'bar' in both a parent and child class, and they would be 2 unique constants only accessable by code inside their class definition (how it works right now). I take this position because, from a programmer standpoint, when i type 'const foo', I would like to believe I'm defining a *constant* name/value pair that could not be changed at runtime. If constants are aggregated together between all parent/child class definitions on instantiation, then there could be the potential for conflicts and overriding of constant values. But I digress.. I just would like an easy way to get to child constants. Having to use something like myClass::constant from *inside* an object instance *of* myClass seems kind of... strange. I imagine that conversation looking something like this: Scene: A family instance of a Parent and Child are standing side by side. They talk amongst themselves for a few moments, and then.. Parent speaks: "Mr. Zend Engine, I need to access a static class constant." Zend: "OK, Parent, a constant of whom?" Parent: "A constant of my Child. Do you know where I can find my Child?" Zend: "Errr.. well.. yeah.. your Child is right there!" *points finger* Parent: "Hey! There's my Child and he's got the constant! Thanks Zend, you know *everything*!" Zend: *scratches head and thinks* "Why didn't he just ask him himself instead of wasting my time?" Dan Cox

Marcus Börger

22 years ago
Hello Dan, Friday, October 10, 2003, 10:53:20 AM, you wrote:
> Marcus-
> Marcus Börger wrote:
>> Hello Dan, >> >> >>> Currently, I don't see an easy way of accessing the constants of a child >>> class from a base class. >>> >> >> Use the child's class name: >> php -r 'class t{static function f(){echo tt::c;}}class tt extends >> t{const c="Hello\n";} t::f();' >> >> >> > Yes, this is possible, but it assumes you always only ever have one > child class, which > the base class knows the name of (unlikely). You could figure things out > dynamically, > as I provided some example code for, but this would become unwieldy with > 10s to > 100s of derived classes.
>> But i guess you want something dynamically as $class::const, right? >> >> >> > right.
>> the next example uses 'self' which doesn't work because self is bound at >> compile time: >> php -r 'class t{function t(){echo self::c;}}class tt extends t{const >> c= "Hello\n";} $o=new tt;' >> >> So you'd need something like '$this::c' which is impossible right now. >> >> Funny thing is the following which returns NULL what makes absolute no sense >> to me. It somehow looks like either the correct constant is used but it is >> uninitialized or there is an error missing. >> php -r 'class t{function t(){var_dump($this->c);}}class tt extends >> t{const c= "Hello\n";} $o=new tt;' >> >> >> > I believe this is correct behaviour. When instantiating an object the methods, properties, > etc. of the child class and any and all parents are in essence aggregated together into a > new object (the instance). It would make sense (at least to me) that 'class constants' > should NOT be aggregated together on instantiation. They should be constant only for > their particular class definition and all code provided by that class.
> So you could have a const foo = 'bar' in both a parent and child class, and they would > be 2 unique constants only accessable by code inside their class definition (how it works > right now). > I take this position because, from a programmer standpoint, when i type 'const foo', > I would like to believe I'm defining a *constant* name/value pair that could not be > changed at runtime. If constants are aggregated together between all parent/child class > definitions on instantiation, then there could be the potential for conflicts and overriding of > constant values.
> But I digress.. I just would like an easy way to get to child constants. Having to use > something like myClass::constant from *inside* an object instance *of* myClass seems > kind of... strange.
Constants are bound to the class rather then to the objects. Hence they behave pretty much like static properties or default values of declared properties and like the latter they are read only. The only question here is whether we want to be able to access static and/or const class members through something dynamically like $this at runtime. Everything else is perfectly correct in place. And of course constants are public. So perhaps you might want to be able to apply visibility to constants, too? If so i must dissappoint you with the fact that it is currently impossible to do that and the amount of work to enable this seems so high that it is unlike to happen.
-- Best regards, Marcus mailto:helly@php.net

Dan Cox

22 years ago
Hi Marcus- Marcus Börger wrote:
>Constants are bound to the class rather then to the objects. Hence they >behave pretty much like static properties or default values of declared >properties and like the latter they are read only. > >
understood.
>The only question here is whether we want to be able to access static >and/or const class members through something dynamically like $this at >runtime. Everything else is perfectly correct in place. > >And of course constants are public. So perhaps you might want to be able >to apply visibility to constants, too? If so i must dissappoint you with >the fact that it is currently impossible to do that and the amount of work >to enable this seems so high that it is unlike to happen. > > >
So, in order to access class constants from outside the class, the class itself must be static? Obviously (I thought for backwards compatability only) all classes are static in PHP5, but looking toward the future, will we continue to have all classes always statically accessible? Dan Cox

Marcus Börger

22 years ago
Hello Dan, Friday, October 10, 2003, 7:37:29 PM, you wrote:
> Hi Marcus-
> Marcus Börger wrote:
>>Constants are bound to the class rather then to the objects. Hence they >>behave pretty much like static properties or default values of declared >>properties and like the latter they are read only. >> >> > understood.
>>The only question here is whether we want to be able to access static >>and/or const class members through something dynamically like $this at >>runtime. Everything else is perfectly correct in place. >> >>And of course constants are public. So perhaps you might want to be able >>to apply visibility to constants, too? If so i must dissappoint you with >>the fact that it is currently impossible to do that and the amount of work >>to enable this seems so high that it is unlike to happen. >> >> >> > So, in order to access class constants from outside the class, the > class itself must be static? Obviously (I thought for backwards > compatability only) all classes are static in PHP5, but looking toward > the future, will we continue to have all classes always statically > accessible?
The class definition is a static thing but several things can only be accessed from instantiated objects. That is a non static method and non static properties can only be accessed via objects. What else should happen? Non static members are instance bound not class bound.
-- Best regards, Marcus mailto:helly@php.net

Wez Furlong

22 years ago
This sounds like you're doing something wrong (no offense!). You want to access a *constant* of a descendant class, when your ancestor doesn't even know if it exists. Well, that sounds more than a little odd (backwards even). Why not just use a property with a known name, and set the value of that property in you descendant class constructor? Performance wise, its not going to make much difference, because no matter what you are doing, to dynamically resolve the value of a constant will involve hash lookups. The other alternative, and this is the official POV of the Zend guys IIRC, is that you can use eval() to look up the value: $node = $doc->createElement(eval(get_class($this) . "::ElementName")); If you think about it, what exactly does child:: refer to anyway? A child class of the current object? But which one? What if the child doesn't have the constant? What if there are interfaces involved? The engine doesn't know about descendant classes either (the inheritance tree works in the other direction), and it shouldn't have to second-guess what your code is doing - its much clearer to explicitly write code like that eval above. Hope that helps! --Wez. ----- Original Message ----- From: "Dan Cox" <dan@wep.net> To: <internals@lists.php.net> Sent: Friday, October 10, 2003 8:10 AM Subject: [PHP-DEV] Accessing child constants from base class
> PHP5 10/10/2003 CVS. > > Currently, I don't see an easy way of accessing the constants of a child > class from a base class. Consider the following basic example: > > abstract class myBaseClass { > function getXML() { > $doc = new domDocument(); > $node = $doc->createElement(child::ElementName); > $doc->appendChild($node); > return $doc->saveXML($node); > } > } > > class myChildClass extends myBaseClass { > const ElementName = 'foo'; > // ..lots of methods, etc here.. > } > > $foo = new myChildClass(); > print($foo->getXML()); > > > Of course, this does not work as there is no 'child::' accessor. The > alternatives > to making this work are: > > Use a protected variable in the child (and, of course, don't forget to > also say > 'protected $ElementName;' in the base class) and use $this->ElementName > for accessing the child's ElementName from the parent. This is probably
good
> enough, but much less than ideal. > > Use this beautifully low maintenance, high performance piece of code in
the
> base class (not): > > $className = get_class($this); > switch($className) { > case 'mychildclass': > $childElementName = myChildClass::ElementName; > break; > case 'myotherchildclass': > $childElementName = myOtherChildClass::ElementName; > break; > // etc..... > } > .... ick! > > You may be able to shortcut the need for the switch statement with some > variable variable and/or eval() trickery, but let's not even go there.. > > So are there any plans on implementing child:: ? Is there a way of
accessing

Ard Biesheuvel

22 years ago
How about an abstract method in the base class called getElementName(), which each child will implement to return its element name.
-- Ard

Mohamed Lrhazi

22 years ago
I am running into a complex PHP problem, where PHP, different versions I tried, under redhat or mandrake, dies with sig fault in a way that I could not clearly reproduce... there are different components involved and I need help narrowing this down.... Is this the right list? Thanks alot. Mohamed~

Dan Cox

22 years ago
Ard Biesheuvel wrote:
> How about an abstract method in the base class called > getElementName(), which each child will implement to return its > element name. >
Hi Ard- This wouldn't work because the abstract method in the base class is still running in the 'base class scope' and can't see the child's constants. Dan Cox

Ard Biesheuvel

22 years ago
This works fine for me: abstract class Base { abstract function getElementName(); function getName() { return $this->getElementName(); } } class Derived extends Base { const ElementName = 'DerivedElementName'; function getElementName() { return ElementName; } } $c = new Derived(); echo $c->getName();
-- Ard

Dan Cox

22 years ago
Hi Ard- Sorry, I must of misunderstood you. Yes, that would work fine. Now copy/paste the getElementName() function in the derived class to all 100's of other derived classes and wonder why you couldn't just do this with one base class function. :) Dan Cox Ard Biesheuvel wrote:

Marcus Börger

22 years ago
Hello Wez, Friday, October 10, 2003, 12:44:07 PM, you wrote:
> This sounds like you're doing something wrong (no offense!).
> You want to access a *constant* of a descendant class, when > your ancestor doesn't even know if it exists. > Well, that sounds more than a little odd (backwards even).
It does sound odd. But anyway he raised an intersting question feature/behavioral wise.
-- Best regards, Marcus mailto:helly@php.net

Dan Cox

22 years ago
Hi Wez- Wez Furlong wrote:
>This sounds like you're doing something wrong (no offense!). > >You want to access a *constant* of a descendant class, when >your ancestor doesn't even know if it exists. >Well, that sounds more than a little odd (backwards even). > > >
>Why not just use a property with a known name, and set the >value of that property in you descendant class constructor? > > >
Yes, this is how I'm doing things now. It just seems like I shouldn't be forced to always have to use parent::__construct(_MY_CONSTANT_)
>Performance wise, its not going to make much difference, >because no matter what you are doing, to dynamically resolve >the value of a constant will involve hash lookups. > >The other alternative, and this is the official POV of the >Zend guys IIRC, is that you can use eval() to look up >the value: > >$node = $doc->createElement(eval(get_class($this) . "::ElementName")); > > >
Yes. Normally, at least with most other programming languages, using eval() is a major performance hit (as much as 10x slower), so it shouldn't be used unless there is absolutely no other way. Maybe this isn't the case with PHP?
>If you think about it, what exactly does child:: refer to anyway? >A child class of the current object? But which one? What if >the child doesn't have the constant? What if there are interfaces >involved? > > >
A deriving class of the current *instance* .. so childInstance:: then :) Perhaps you could use interfaces to enforce children having the needed constants.
>The engine doesn't know about descendant classes either (the >inheritance tree works in the other direction), and it shouldn't >have to second-guess what your code is doing - its much clearer to >explicitly write code like that eval above. > >Hope that helps! > >--Wez. > > >
I suppose. It just seems odd that a class can talk to it(self::) and it's parent:: but not its child:: even though the child:: instance started the conversation in the first place :) Dan Cox

Wez Furlong

22 years ago
> >Performance wise, its not going to make much difference, > >because no matter what you are doing, to dynamically resolve > >the value of a constant will involve hash lookups. > > > >The other alternative, and this is the official POV of the > >Zend guys IIRC, is that you can use eval() to look up > >the value: > > > >$node = $doc->createElement(eval(get_class($this) . "::ElementName")); > > > Yes. Normally, at least with most other programming languages, > using eval() is a major performance hit (as much as 10x slower), > so it shouldn't be used unless there is absolutely no other way. > Maybe this isn't the case with PHP?
It'll be slow no matter what you do, because you need to dynamically reference the constant value. You could also use the switch construct you already posted, or use the solution suggested by Ard; all of these are slow, particularly switch when used with a large number of string 'case's. You might actually find that the eval works out faster. If you're really thinking of writing high performance code in PHP, you shouldn't be writing code that has 100's of class definitions ;-)
> I suppose. It just seems odd that a class can talk to it(self::) > and it's parent:: but not its child:: even though the child:: > instance started the conversation in the first place :)
The engine only stores child->parent relationships, not child<->parent relationships. Think about it for a moment... can you do this kind of thing in compiled languages? Do you know why you can't? The reason is that the compiler has no way of knowing what classes are going to extend it at the time it compiles the base class. This is why I suggested that trying to dynamically access a constant (eg: compile time!) of a child class just seems wrong. --Wez.

Dan Cox

22 years ago
Wez- Wez Furlong wrote:
>>>Performance wise, its not going to make much difference, >>>because no matter what you are doing, to dynamically resolve >>>the value of a constant will involve hash lookups. >>> >>>The other alternative, and this is the official POV of the >>>Zend guys IIRC, is that you can use eval() to look up >>>the value: >>> >>>$node = $doc->createElement(eval(get_class($this) . "::ElementName")); >>> >>> >>> >>Yes. Normally, at least with most other programming languages, >>using eval() is a major performance hit (as much as 10x slower), >>so it shouldn't be used unless there is absolutely no other way. >>Maybe this isn't the case with PHP? >> >> > >It'll be slow no matter what you do, because you need to dynamically >reference the constant value. You could also use the switch construct >you already posted, or use the solution suggested by Ard; all of these >are slow, particularly switch when used with a large number of string >'case's. You might actually find that the eval works out faster. > >If you're really thinking of writing high performance code in PHP, >you shouldn't be writing code that has 100's of class definitions ;-) > > >
hehe. Normally, I'd agree with 100's of class definitions being a bad idea, but my particular case warrants it. Basically, I'm using PHP's new DOM features to create an API to easily build XML document fragments. It seems to make sense that each derived class be responsible for building only the bit of XML it should know about. All of these derived classes then extend an abstract class which provides the children with getXML() functionality. The code using the API then uses the objects to build a full XML document (based on a particular XML Schema). Another reason for using the derived classes is for enforcing data types, etc. at the application level, which allows me to throw informative Schema_Exception() type errors. The new dom->validate() functionality is nice, but not so useful when building an XML document yourself. AFAIK it also only works with DTDs and not XML Schemas (the XML Schema support for libxml2 is... quite lacking at this time). Of course, I'm also using PHP's new __autoload() feature to keep performance up. :) On a side note, when an exception is thrown from inside the __autoload() function, PHP only reports 'exception thrown in __autoload' and no other information about the exception. Is this a bug?
>> suppose. It just seems odd that a class can talk to it(self::) >>and it's parent:: but not its child:: even though the child:: >>instance started the conversation in the first place :) >> >> > >The engine only stores child->parent relationships, not child<->parent >relationships. Think about it for a moment... can you do this kind >of thing in compiled languages? Do you know why you can't? >The reason is that the compiler has no way of knowing what classes are >going to extend it at the time it compiles the base class. > >This is why I suggested that trying to dynamically access a constant >(eg: compile time!) of a child class just seems wrong. > >--Wez. > >
I understand. For some reason, I just thought that self:: and parent:: were evaluated at runtime instead of compile time. I believe I will just stick with a protected variable in the derived classes instead of a constant. This seems the easiest (and safe enough in my case) approach. Thanks- Dan Cox

Marcus Börger

22 years ago
Hello Dan, Friday, October 10, 2003, 8:36:48 PM, you wrote:
> On a side note, when an exception is thrown from inside the > __autoload() function, PHP only reports 'exception thrown in > __autoload' and no other information about the exception. Is > this a bug?
I don't think so. The exception shows as much information as is available. Only sometimes the exception is generated deeply inside the engine where not many additional information can be accessed. And don't forget: Exceptions should be exceptions.
> I understand. For some reason, I just thought that self:: and > parent:: were evaluated at runtime instead of compile time.
Hope you get that straight now. parent & self must be considered beeing compiletime things.
-- Best regards, Marcus mailto:helly@php.net

Dan Cox

22 years ago
Marcus- Here are just some interesting observations with __autoload() and exceptions: Currently, an empty php file with just "throw new Exception();" will dump out an error like so: *Fatal error*: Uncaught exception 'exception' with message 'Unknown exception' in /server/http/sites/db.wep.net/htdocs/test.php:2 Stack trace: #0 {main} thrown in* /usr/local/apache/htdocs/test.php* on line *2 * OK, that's expected.. Now try something like this: function __autoload($x) { throw new Exception(); } $foo = new bar(); Will just give you: *Fatal error*: __autoload threw an exception in* /usr/local/apache/htdocs/test.php* on line *3* NOTE: The exception does not actually have to happen inside the __autoload function. It can occur in another function the autoloader calls with the same results. Obviously, exceptions *should* be caught and none of the above is valid code. I've seen a few bug reports other people filed in regards to exceptions that were answered with a similar argument like the one you stated.. sometimes exceptions are generated deep inside the engine where not much information is available. Taken at face value, that sounds perfectly reasonable; however another way of saying that would be "The handling of fatal, script ending, runtime exceptions will be mostly consistent, but sometimes may contain minimal or even useless information depending on where it occurred." :) This behaviour isn't a show stopper. It's just the inconsistency that bugs me. I'm left wondering what other ways the engine might handle my exceptions that I might not expect, like... try { function __autoload($x) { throw new Exception(); } $foo = new bar(); } catch (Exception $e) { print('caught exception'); } The exception will never be caught in this case (resulting in the same previous error). A PHP engine developer might look at that and say something like "oh, of course it doesn't. The engine removes the special __autoload function from the main try/catch block at compile time." Now try explaining why it would do this to your average PHP user. ;) Luckily, using a try/catch block *inside* the __autoload function works fine. Its just not at all able to pass the exception on up the chain. Meaning people need to keep in mind that any and all objects/methods/etc. which could throw exceptions that may have been called from inside their __autoload function will only get those exceptions passed as high as the autoloader for handling instead of their main code. If this is intended behaviour, then this is just a recommendation for a note in the future documentation of __autoload. Dan Cox Marcus Börger wrote:

Marcus Börger

22 years ago
Hello Dan, Sunday, October 12, 2003, 7:34:59 AM, you wrote:
> This behaviour isn't a show stopper. It's just the inconsistency that > bugs me.
yes!
> I'm left wondering what other ways the engine might handle my exceptions > that > I might not expect, like...
> try { > function __autoload($x) { throw new Exception(); } > $foo = new bar(); > } > catch (Exception $e) { print('caught exception'); }
> The exception will never be caught in this case (resulting in the same previous > error). A PHP engine developer might look at that and say something like "oh, of > course it doesn't. The engine removes the special __autoload function from the > main try/catch block at compile time." Now try explaining why it would do this to > your average PHP user. ;)
Yes, that's a valid point that goes to you. But this can be delayed until after PHP 5 release right?
> Luckily, using a try/catch block *inside* the __autoload function works fine. Its just > not at all able to pass the exception on up the chain. Meaning people need to keep > in mind that any and all objects/methods/etc. which could throw exceptions that may > have been called from inside their __autoload function will only get those exceptions > passed as high as the autoloader for handling instead of their main code.
> If this is intended behaviour, then this is just a recommendation for a note in the > future documentation of __autoload.
As said lat's delay and document it. Maybe someone finds an appropriate nice solution later. Feel free to make this a documnetation bug.
-- Best regards, Marcus mailto:helly@php.net

Andi Gutmans

22 years ago
Dan, __autoload() is the last chance to load a class which is needed before erroring out and terminating the script. By design, if you don't take advantage of this last chance the script will error out. Therefore, throwing an exception inside the __autoload() function should not be catchable by your script. I hope that explains things. Andi At 01:34 AM 10/12/2003 -0400, Dan Cox wrote:

Dan Cox

22 years ago
Hi Andi- Yes, that explains things and makes perfect sense if you are just writing your own web application. Now, consider the new __autoload functionality from a PEAR or other large API/library developer standpoint. The new __autoload functionality lets us: - Keep library usage/installation simple by only requiring the user to include one include file regardless of how many possible classes or other files may be needed. This 'initialization' file will setup a special autoloader for just that library. If the user happens to use their own autoloader, then they have to simply setup something like- if (eregi('^libclass_', $className)) { somelib::autoload($className); } A 1, with an optional 2 step install process, regardless of which parts of the library may be used is a good thing. :) - Keeps the performance of the library up because it now only needs to load class objects the user actually uses. The new exception handling also lets a library developer easily document and let a user know that mylib will throw exception x, y and z. With that in mind, it's always good practice when writing an API/library to throw the most informative errors possible. Normally, this would also include errors such as "Unable to initialize library 'mylib' because file 'foo.inc' could not be found. Please check install documentation.". A much nicer exception/error than Fatal Error in mylib. This would also allow the user to catch the error and handle it themself, and at their sole discretion choosing to continue on with script execution. These are just my thoughts and observations after writing a large API/library in PHP5- Dan Cox Andi Gutmans wrote: