Class Constant Visibility Modifiers #1494
|
@marcioAlmada thanks for linking that! When I hear back from @reeze and hear if he is ok with me adopting/working with him on the RFC I will add it to that page (or I will have to start my own) |
|
Hi @Sean-Der Thanks for your work. I didn't finish my patch but start to working on other things. You are welcome to working on the RFC. |
|
Thanks for the quick response @reeze are you interested in working with me on it? I totally understand if you are busy/not interested anymore, I just want to make sure I am not stomping on your work/ideas. thanks! |
|
Hi @Sean-Der Go ahead ;-) I will test your patch and try to contribute to your branch once I have time. |
|
One question for the RFC: why the syntax used in interface is different in class? could we make it consistent? I saw the constant defined in interface use interface ICache {
protected const PROTECTED = 0;
}but the constants defined in class are just: class Foo {
protected FOO = 0;
public BAR = 10;
} |
|
@c9s your 2nd example is not valid syntax, see https://3v4l.org/ST9Gp |
|
@staabm The code was copied from RFC and it's only in RFC. (this one https://wiki.php.net/rfc/class_const_visibility ) |
|
I think changing the code below (The RFC one): class Token {
// Constants default to public
const PUBLIC_CONST = 0;
// Constants then also can have a defined visibility
private PRIVATE_CONST = 0;
protected PROTECTED_CONST = 0;
public PUBLIC_CONST_TWO = 0;
}to this one is better: class Token {
// Constants default to public
const PUBLIC_CONST = 0;
// Constants then also can have a defined visibility
private const PRIVATE_CONST = 0;
protected const PROTECTED_CONST = 0;
public const PUBLIC_CONST_TWO = 0;
} |
|
I dont think its necessary. Constants are supposed to be public anyway, unless you mean read-only fields, which can be private or protected. |
|
So, is this going to be merged or not? One more question: If a constant is defined as protected constant in one interface, is it available to classes implemented with the interface? |
|
I believe the inconsistent examples in the RFC are just an oversight. It indeed should be: class { private const PRIVATE_CONST = 0; } // valid
class { private PRIVATE_CONST = 0; } // invalid!
For major language changes we have a voting phase. You can watch the votings through http://php-rfc-watch.beberlei.de |
|
Hey @marcioAlmada @c9s yep you caught a typo! Pretty embarrassing for such a light RFC, thank you @HallofFamer In PHP I don't think you can have read-only fields (properties?) now. There are a couple of use cases for a readonly member of a class constant, check out the conversation so far about this here |
|
@Furgas usually RFCs are hard work and authors tend to add reflection API after the discussion or voting phase, when they already know it's worth committing more time to it. |
|
Thanks for sticking up for me @marcioAlmada appreciate it, OpenSource is usually less than welcoming. Thanks @Furgas for being interested in this RFC! getDocComment is now implemented by 47bdc4a |
|
What will be your stance regarding ReflectionClass and it's public API? Currently:
You're changing this behavior, don't you? The "RFC impact" section must be explicit about it :) |
|
@marcioAlmada Yep those should be changed as well! I will make those changes and update the RFC, have plenty of failing tests also. Trying to hit the bare minimum to just get the RFC approved. Thanks for looking it all over! thanks! |
| + const_info = pemalloc(sizeof(zend_class_constant_info), 1); | ||
| + } else { | ||
| + const_info = zend_arena_alloc(&CG(arena), sizeof(zend_class_constant_info)); | ||
| + } | ||
| + | ||
| + const_info->offset = ce->constants_count++; | ||
| + const_info->name = zend_string_copy(name); | ||
| + const_info->name = zend_new_interned_string(const_info->name); | ||
| + const_info->flags = access_type; | ||
| + const_info->doc_comment = doc_comment; | ||
| + const_info->ce = ce; | ||
| + | ||
| + ce->constants_table = perealloc(ce->constants_table, sizeof(zval) * ce->constants_count, ce->type == ZEND_INTERNAL_CLASS); | ||
| + zend_hash_add_ptr(&ce->constants_info, name, const_info); | ||
| + } else { | ||
| + i_zval_ptr_dtor(&ce->constants_table[const_info->offset] ZEND_FILE_LINE_CC); |
|
Sean-Der
The old behavior was to just update the value of the constant if it already existed, and some extensions in tree now depend on it (Date being one of them) I thought better to keep the existing behavior, because maybe extensions outside php-src depend on this? |
| - if (CG(compiler_options) & ZEND_COMPILE_NO_PERSISTENT_CONSTANT_SUBSTITUTION) { | ||
| - return 0; | ||
| - } | ||
| + if (!zend_verify_const_access(const_info, EG(current_execute_data) ? EG(scope) : CG(active_class_entry))) { |
|
nikic
Given this code is in zend_compile, should this always use |
| { | ||
| + zend_class_constant_info *const_info; | ||
| + | ||
| + if (zend_string_equals_literal_ci(name, "class")) { | ||
| + zend_error(E_CORE_ERROR, | ||
| + "A class constant must not be called 'class'; it is reserved for class name fetching"); |
|
nikic
Note: Moving the check in here changes the error from E_COMPILE_ERROR to E_CORE_ERROR. I'd probably choose between these to depending on whether it's an internal class or not. |
| @@ -296,6 +296,18 @@ typedef struct _zend_property_info { | ||
| #define OBJ_PROP_TO_NUM(offset) \ | ||
| ((offset - OBJ_PROP_TO_OFFSET(0)) / sizeof(zval)) | ||
| +typedef struct _zend_class_constant_info { | ||
| + uint32_t offset; /* offset for values in (zend_class_entry*)->constants_table */ | ||
| + uint32_t flags; | ||
| + zend_string *name; | ||
| + zend_string *doc_comment; | ||
| + zend_class_entry *ce; | ||
| +} zend_class_constant_info; | ||
| + | ||
| + | ||
| +#define OBJ_CONST_NUM(obj, num) \ | ||
| + (&(obj)->constants_table[(num)]) |
|
nikic
As this isn't working on an object,
Sean-Der
This is just copied from |
| @@ -330,6 +343,14 @@ ZEND_API zval *zend_get_constant_ex(zend_string *cname, zend_class_entry *scope, | ||
| } | ||
| } | ||
| + if (!current_scope) { | ||
| + if (EG(current_execute_data)) { |
|
nikic
Not sure, but probably |
| @@ -3723,58 +3724,151 @@ ZEND_API int zend_declare_property_stringl(zend_class_entry *ce, const char *nam | ||
| } | ||
| /* }}} */ | ||
| -ZEND_API int zend_declare_class_constant(zend_class_entry *ce, const char *name, size_t name_length, zval *value) /* {{{ */ | ||
| +ZEND_API int zend_declare_class_constant_ex(zend_class_entry *ce, zend_string *name, zval *value, int access_type, zend_string *doc_comment) /* {{{ */ |
|
bwoebi
The RFC says we are mirroring method visibilities in interfaces. As far as I see, this method does not prohibit private and protected visibilities for class constants in interfaces… I guess the check was just missed … copied from if (ce->ce_flags & ZEND_ACC_INTERFACE) {
if (access_type != ZEND_ACC_PUBLIC) {
zend_error_noreturn(E_COMPILE_ERROR, "Access type for interface constant %s::%s must be omitted", ZSTR_VAL(ce->name), ZSTR_VAL(name));
}
}
Danack
The RFC specifically says that protected and public constants in an interface are allowed.
I've asked on list for it to be clarified. Most people seem to be against the idea of protected constants. So it would be better to explicitly exclude that from the RFC, to avoid someone adding it as a 'bug-fix' later.
Sean-Der
This is fixed in the RFC! I will be pushing more commits to fix the (many) issues people have found so far. Adding the ZEND_ACC_PUBLIC check for interfaces. |
| - zval *old_constant; | ||
| - | ||
| - if ((old_constant = zend_hash_find(child_constants_table, name)) != NULL) { | ||
| - if (!Z_ISREF_P(old_constant) || | ||
| - !Z_ISREF_P(parent_constant) || | ||
| - Z_REFVAL_P(old_constant) != Z_REFVAL_P(parent_constant)) { | ||
| - zend_error_noreturn(E_COMPILE_ERROR, "Cannot inherit previously-inherited or override constant %s from interface %s", ZSTR_VAL(name), ZSTR_VAL(iface->name)); | ||
| - } | ||
| + zend_class_constant_info *old_const_info; | ||
| + | ||
| + if ((old_const_info = zend_hash_find_ptr(child_constants_table, name)) != NULL) { | ||
| + if ((old_const_info != parent_constant) && | ||
| + (OBJ_CONST_NUM(old_const_info->ce, old_const_info->offset) != | ||
| + OBJ_CONST_NUM(parent_constant->ce, parent_constant->offset))) { | ||
| + zend_error_noreturn(E_COMPILE_ERROR, "Cannot inherit previously-inherited or override constant %s from interface %s", ZSTR_VAL(name), ZSTR_VAL(iface->name)); | ||
| + } |
|
nikic
Do we need a check to forbid constants from going public -> protected during inheritance? Also, I feel like something is missing in the inheritance code to manage constant_table. Where is it resized for more constants?
nikic
Also, won't private constants be inherited as well? Even if I'm wrong, a testcase like this would be nice:
This should throw an error.
Sean-Der
Adding that test cases + fixing, thanks for catching both issue! Both should be fixed by 0c250e2
nikic
Allowing a public -> protected change violates LSP. We have this check in place for methods: https://3v4l.org/5adLa |
| @@ -5935,7 +5936,13 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_FETCH_CONSTANT_SPEC_CONST_CONS | ||
| } | ||
| } | ||
| - if (EXPECTED((value = zend_hash_find(&ce->constants_table, Z_STR_P(EX_CONSTANT(opline->op2)))) != NULL)) { |
|
nikic
You're directly modifying zend_vm_execute here. You should change zend_vm_def instead and regenerate using zend_vm_gen.php |
|
Hi, Also, there seems to be a conflict in trait resolution for constants and methods with same name (based on an example from http://news.php.net/php.internals/88906). What will this code do? trait TestTrait {
const foo = 'const';
function foo() { echo 'method'; }
}
class TestClass {
use TestTrait { foo as bar; }
} |
|
@Majkl578 that example was wrong (it was mine) sorry please ignore the trait stuff. I am sorry if the Reflection changes cause any issues for you! However, if the change was not made we would be missing out on important information (flags + docComments) It will be a while before this code ends up on production machines though (7.1 is a long time away) |
|
Hi!
First of all: currently, traits cannot have constants so the problem doesn't even exists https://3v4l.org/gCvYH. Even if we add constants to traits one day (and I hope this day never comes), we could have: trait TestTrait {
const foo = 'const';
function foo() { echo 'method'; }
}
class TestClass {
use TestTrait {
const foo as bar;
foo as bar;
}
}I don't see any issue here Regarding the Reflection API break, @nikic proposed an alternative. Instead of changing the return values of existing methods, we could just include Seems reasonable, even though I prefer the BC break. |
|
@marcioAlmada I prefer the BC break as well. My rationale is
But always happy to take to a vote :) |
|
I think it's better if you can squash all you changes into one commit... , there are lots of commits now |
|
@laruence done! Are you able to reproduce those test failures on a ZTS build? My CLI SAPI returns
But |
|
@Sean-Der The debug build on Travis also enabled opcache. That's likely where the issue is. |
|
Thanks for the clarification about traits, that's not an issue for now. I understand your reasons for breaking compatibility in relfection, but this is an excellent example of a change that becomes a nighmare for projects that need to keep compatibility with multiple versions of PHP. And since this RFC targets 7.1, it should also be noted that it violates PHP's release process, which prohibits userland API BC breaks in x.y+1.z versions. |
|
@Majkl578 That is a great point, what do you think of not breaking the old API at all and doing @marcioAlmada's suggestion? Adding two new subroutines |
|
And done! I implemented @marcioAlmada's suggestion, and no more BC in this commit anymore. AFAIK this is ready for review (and hopefully merge) if anyone has any suggestions/guidance on better tests or who to bother to get the merge process going would love to hear! |
|
@dstogov Hey! Would you mind reviewing the opcache changes, the entire test suite does pass though. thanks |
| HashTable function_table; | ||
| HashTable properties_info; | ||
| - HashTable constants_table; | ||
| + HashTable constants_info; |
|
dstogov
I think, it's going to be more efficient to introduce zend_class_constant structure and keep such structures in HashTable constants_table. We don't need separate "info" and "table" because, in opposite to properties, constants can't be changed in run-time.
Sean-Der
Lucky to get a review out of you! Thanks for taking a careful look at this, and keeping php-src in top shape :) If there is anything I can do code wise, just tell me. Would love to stay involved, but it is more efficient for you to work alone. thanks |
|
Please review another PR based on this one #1662 |
|
close this, focus on #1662 instead |
@reeze created an RFC here and I emailed internals here and didn't get any responses positive/negative.
This is a WIP and has test fails, and the API has not been finalized. Maybe it should be changed to mirror properties more, but this would cause more breakage.