Req #53975 [Opn]: Late constant define (class const to class name translation)
| From: | nikic@php.net | Date: | Sat, 16 Apr 2016 14:00:37 +0000 |
| Subject: | Req #53975 [Opn]: Late constant define (class const to class name translation) | ||
| References: | 1 | Groups: | php.bugs |
| Request: | Send a blank email to php-bugs+get-200596@lists.php.net to get a copy of this message | ||
Edit report at https://bugs.php.net/bug.php?id=53975&edit=1
ID: 53975
Updated by: nikic@php.net
Reported by: landeholm at gmail dot com
Summary: Late constant define (class const to class name
translation)
Status: Open
Type: Feature/Change Request
Package: Scripting Engine problem
Operating System: Irrelevant
PHP Version: 5.3.5
Block user comment: N
Private report: N
New Comment:
PHP 5.5 introduced the Bar::class syntax, which will resolve class Bar to a fully qualified name.
Previous Comments:
------------------------------------------------------------------------
[2011-02-09 22:55:58] landeholm at gmail dot com
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 this bug report at https://bugs.php.net/bug.php?id=53975&edit=1