|  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Request #53975 Late constant define (class const to class name translation)
Submitted: 2011-02-09 22:55 UTC Modified: 2016-04-16 14:00 UTC
Avg. Score:4.3 ± 0.9
Reproduced:3 of 3 (100.0%)
Same Version:1 (33.3%)
Same OS:1 (33.3%)
From: landeholm at gmail dot com Assigned:
Status: Open Package: Scripting Engine problem
PHP Version: 5.3.5 OS: Irrelevant
Private report: No CVE-ID: None
Have you experienced this issue?
Rate the importance of this bug to you:

 [2011-02-09 22:55 UTC] landeholm at gmail dot com

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.


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:

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.


Test script:
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)

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


Add a Patch

Pull Requests

Add a Pull Request


AllCommentsChangesGit/SVN commitsRelated reports
 [2016-04-16 14:00 UTC]
PHP 5.5 introduced the Bar::class syntax, which will resolve class Bar to a fully qualified name.
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Wed Jul 24 09:01:30 2024 UTC