From:             
Operating system: Irrelevant
PHP version:      5.3.5
Package:          Scripting Engine problem
Bug Type:         Feature/Change Request
Bug description:Late constant define (class const to class name translation)

Description:
------------
Hello,



I'm making a framework which utilizes namespaces and classes. One thing I
like with PHP is how you can work with class names like normal strings.
There's no special "Type" or "ClassType" object that wraps this behavior
and identifies a particular class. A handle to a class is simply a string
with the same name. All good and well. So you start to juggle with class
names and makes functions that accept the names of classes. One thing I do
in my framework is giving the ability to preinitialize objects in classes
with the class you define. Like:



namespace misc\some_ns;



class Foo extends Something {

    public $bar = "misc\some_ns\Baz";

}



I do this since PHP doesn't allow object instances ( = new Baz()) as the
default value of object variables. I understand that this would be
problematic since for example get_class_vars etc would have to instantiate
class objects in it's return values. Implicit object construction can
result in undesired side-effects.



I also work with class names in a bunch of other ways. An annoyance however
is that the class name in a string is naturally not affected by the current
namespace you're in, requiring you to literally have to "think out of the
box" whenever you type a class name. Also since you're typing inside of a
string there's no auto completion. Removing the quotes I can CTRL+Space (in
netbeans) the smart resolved class name in no time, but adding the quotes
requires you to type the full class name without any help.



This is basically how I'd rather want to express the above which is much
cleaner and better understood by IDE's (I could for example track the
references of the Baz class and see that it's used on this line):



namespace misc\some_ns;



class Foo extends Something {

    public $bar = Baz;

}



It's unfair, really, that PHP identifiers like new, clone, instanceof etc
gets to use the magic and awesome namespace aware string-less direct class
name resolving. Why not allow this behavior in any context and not just in
the presence of these identifiers? You can already juggle with class names
in strings and use strings dynamically with these identifiers, so
implementing automatic class-identifier-to-full-classname resolving would
be in spirit with the existing behavior.



To implement this, change the way constants are resolved to:



1. If constants exists with same name return constant value.



2. *new* If namespace aware class exists return full class name.



3. Return constant name as string and trigger "assuming constant name"
error.



The obvious problem with this however is that it would not be compatible
with autoloading. Probing the autoloader can be dangerous as some autoload
implementations might throw exceptions/errors/halt the script etc if the
class name doesn't exist. In fact the official autoload documentation
suggests this.



Therefore I like to suggest a completely different implementation of this
solution that not only enables constant class name resolving with autoload
support but increase flexibility greatly when designing frameworks:
automatic late constant define. By allowing the programmer to define
constants as they are used (much the same way classes are defined when they
are first used with autoload) this behavior and many other unspecified
late-constant binding behaviors could be implemented.



Example:



Take the constant resolution steps above but changed to the following:



1. If constants exists with same name return constant value.



2. *new* Invoke SPL constant "autodefine" function stack. If one of them
return a value for it the constant is defined with that value.



3. Return constant name as string and trigger "assuming constant name"
error.



The following new functions would be implemented: (for example)



spl_autodefine_call( string $name )

spl_autodefine_functions( void )

spl_autodefine_register( callback $autodefine_function [, bool $prepend =
false ]] )

spl_autodefine_unregister( callback $autodefine_function )



These would work much the same way as the spl_autoloads but be used for
constant resolving instead. By default the autodefine function stack would
be empty and 2. be skipped. If a function has been registered with
spl_autodefine_register(), that function will be called with the constant
name as it's first and only argument. The function can now choose to
declare this constant or not. The return value is ignored. [[One possible
implementation would be to declare the constant with the return value and
use NULL as a placeholder if the function don't understand the constant.
But then a constant could not be declared as NULL.]] If the function did
not declare the constant the next function is called.



The normal resolution rules would apply for ambiguous constants (constants
without namespace in them) so they would be called twice in accordance with
the namespace rules:
http://www.php.net/manual/en/language.namespaces.rules.php



For example:



namespace foo;



$var = bar; // undeclared constant



Here the spl_autodefine callback is first called with 'foo\bar' in the
first attempt. If no declaration is made the stack is called again with
'bar' in a second attempt. In this case, after failing to resolve
'foo\bar', php adds 'foo\bar' to an internal table of spl_autodefine
constants that was failed to be resolved. The constants in this internal
table will be ignored and not be attempted to be resolved by spl_autodefine
unless the spl_autodefine stack is changed in which case the table is
cleared. The autodefine function is thus assumed to be deterministic.
Requiring a constant defining function to be constant is common sense after
all. The exact resolution order for ambiguous constants "bar" in namespace
"foo" would thus be:



foreach (array("foo\bar", "foo") as $constant_name)

{

1/3. If $constant_name is defined return it.



2/4. *new* If $constant_name has not already been probed by spl_autodefine,
call spl_autodefine stack, then if $constant_name is defined return it,
otherwise mark $constant_name as already probed.

}



5. Return constant name as string and trigger "assuming constant name"
error.



Additionaly the signature of defined could be changed much like
class_exists:

defined ( string $name [, bool $autodefine = true ] )



If this is implemented, this would provide fantastic freedom for framework
architecture. For example, I use constants for my application
configuration. However if new configuration directives are added the
application will crash since a non declared constant is used. If something
like spl_autodefine existed I could simply auto define the constant with an
appropriate default value. My current solution is basically parsing the
configuration file and injecting configuration that is missing. It works
but is far from being a nice solution, modifying PHP code with PHP is in
general bad and the default value of the constant gets hard coded and
cannot be changed.



Thanks!

Test script:
---------------
<?php

namespace really\long\name_space\name;



spl_autodefine_register(function($name) {

        // This could actually autoload the $name class enabling

        // late class loading and late constant defining to work together.

        if (\class_exists($name))

                define($name, $name);

});



abstract class Foo {

        static function doWork() {

                echo "Did some work in " . get_called_class() . "\n";

        }

}



class Bar extends Foo {}



class Baz extends Foo {}



class Biz extends Foo {}



foreach (array(Bar, Baz, Biz) as $class_name)

        $class_name::doWork();



Expected result:
----------------
Did some work in really\long\name_space\name\Bar

Did some work in really\long\name_space\name\Baz

Did some work in really\long\name_space\name\Biz

Actual result:
--------------
Fatal error: Call to undefined function
really\long\name_space\name\spl_autodefine_register() in test.php on line
4





-- 
Edit bug report at http://bugs.php.net/bug.php?id=53975&edit=1
-- 
Try a snapshot (PHP 5.2):            
http://bugs.php.net/fix.php?id=53975&r=trysnapshot52
Try a snapshot (PHP 5.3):            
http://bugs.php.net/fix.php?id=53975&r=trysnapshot53
Try a snapshot (trunk):              
http://bugs.php.net/fix.php?id=53975&r=trysnapshottrunk
Fixed in SVN:                        
http://bugs.php.net/fix.php?id=53975&r=fixed
Fixed in SVN and need be documented: 
http://bugs.php.net/fix.php?id=53975&r=needdocs
Fixed in release:                    
http://bugs.php.net/fix.php?id=53975&r=alreadyfixed
Need backtrace:                      
http://bugs.php.net/fix.php?id=53975&r=needtrace
Need Reproduce Script:               
http://bugs.php.net/fix.php?id=53975&r=needscript
Try newer version:                   
http://bugs.php.net/fix.php?id=53975&r=oldversion
Not developer issue:                 
http://bugs.php.net/fix.php?id=53975&r=support
Expected behavior:                   
http://bugs.php.net/fix.php?id=53975&r=notwrong
Not enough info:                     
http://bugs.php.net/fix.php?id=53975&r=notenoughinfo
Submitted twice:                     
http://bugs.php.net/fix.php?id=53975&r=submittedtwice
register_globals:                    
http://bugs.php.net/fix.php?id=53975&r=globals
PHP 4 support discontinued:          http://bugs.php.net/fix.php?id=53975&r=php4
Daylight Savings:                    http://bugs.php.net/fix.php?id=53975&r=dst
IIS Stability:                       
http://bugs.php.net/fix.php?id=53975&r=isapi
Install GNU Sed:                     
http://bugs.php.net/fix.php?id=53975&r=gnused
Floating point limitations:          
http://bugs.php.net/fix.php?id=53975&r=float
No Zend Extensions:                  
http://bugs.php.net/fix.php?id=53975&r=nozend
MySQL Configuration Error:           
http://bugs.php.net/fix.php?id=53975&r=mysqlcfg

Reply via email to