Here is the updated RFC incorporating the feedback from previous rounds
of discussion.
https://wiki.php.net/rfc/propertygetsetsyntax-v1.2
I'm posting it for final review so I can move to voting on Jan 7th.
Please note that the current fork is not quite up-to-date with the RFC
but will be within a few more days.
-Clint
--
-Clint
As is customary for me, I am voicing my opinion against this proposal.
I do not like the proposed syntax; it is cumbersome and unfamiliar to
current PHP style. I would opt for something more in line with current
method definitions.
I do think that PHP needs something like this proposal, but I dislike
the details of this one. I will vote no on January 7th.
Here is the updated RFC incorporating the feedback from previous rounds of discussion.
A few remaining questions. The RFC makes it clear that ReflectionClass::getMethods() does
not return internal method names like __setSeconds.
-
Are these names visible via
get_class_methods()
/method_exists()
/is_callable()
? -
Inside an accessor, what do FUNCTION and METHOD evaluate as?
-
What happens if a class/subclass contains a regular method __setSeconds?
Steve Clay
A few remaining questions. The RFC makes it clear that
ReflectionClass::getMethods() does not return internal method names
like __setSeconds.
- Are these names visible via
get_class_methods()
/method_exists()
/
is_callable()
?
This is the only remaining point of contention but I would expect
however it is resolved, all methods of reflection would match.- Inside an accessor, what do FUNCTION and METHOD evaluate as?
I would have to test them but they are methods so they should evaluate
as you would expect, I'll test.- What happens if a class/subclass contains a regular method
__setSeconds?
Same thing as a duplicate function declaration error, since that's what
it is, remember that the prefix __ is reserved for php internal use, if
I recall correctly userland code should not be using a double underscore.
Steve Clay
The RFC does not specify whether it's a fatal error to define a class
(directly or via extends/traits) which has both a traditional property
and accessor with the same name, but I think this should be prohibited
to avoid confusion.One might expect this to work if the traditional property is private
in a parent class, but I think even if the patch allowed that special
case (I've not tried it), it should not.
As of the current fork there is no difference between a property and a
property with accessors except that a property with accessors will
always route through the accessor methods. In other words a
property_info structure is created for either type.
Ergo, the same rules apply, a second declaration of a property with the
same name will cause a compilation error.
-Clint
Just getting back to you on #1 and #2...
#1: It seems the majority want to have these internally created accessor
functions visible, which they presently are through get_class_methods,
etc. They are currently hidden by Reflection. I favor the latter, as is
implemented in Reflection since an accessor is "like a method" but is
not quite a method. At any rate, unless anyone voices support for
accessors being hidden from the non-reflection methods, then the current
Reflection changes regarding filtering them from getMethods() will go away.
One alternative here is that I could add a parameter to getMethods()
which would either filter or not filter accessors, which would let
whomever work the way they want to. Defaulting to... which way?
#2:
class a {
public $Foo {
get {
echo "Getting $Foo, FUNCTION = ".FUNCTION.",
METHOD = ".METHOD.PHP_EOL;
return 5;
}
}
}
$o= new a();
echo $o->Foo;
Outputs:
Getting $Foo, FUNCTION = __getFoo, METHOD = a::__getFoo
5
I will add to the RFC that FUNCTION and METHOD work as expected.
A few remaining questions. The RFC makes it clear that
ReflectionClass::getMethods() does not return internal method names
like __setSeconds.
- Are these names visible via
get_class_methods()
/method_exists()
/is_callable()
?
This is the only remaining point of contention but I would expect
however it is resolved, all methods of reflection would match.- Inside an accessor, what do FUNCTION and METHOD evaluate as?
I would have to test them but they are methods so they should evaluate
as you would expect, I'll test.- What happens if a class/subclass contains a regular method
__setSeconds?
Same thing as a duplicate function declaration error, since that's
what it is, remember that the prefix __ is reserved for php internal
use, if I recall correctly userland code should not be using a double
underscore.
Steve ClayThe RFC does not specify whether it's a fatal error to define a class
(directly or via extends/traits) which has both a traditional
property and accessor with the same name, but I think this should be
prohibited to avoid confusion.One might expect this to work if the traditional property is private
in a parent class, but I think even if the patch allowed that special
case (I've not tried it), it should not.
As of the current fork there is no difference between a property and a
property with accessors except that a property with accessors will
always route through the accessor methods. In other words a
property_info structure is created for either type.Ergo, the same rules apply, a second declaration of a property with
the same name will cause a compilation error.
--
-Clint
Here is the updated RFC incorporating the feedback from previous rounds of discussion.
The RFC does not specify whether it's a fatal error to define a class (directly or via
extends/traits) which has both a traditional property and accessor with the same name, but
I think this should be prohibited to avoid confusion.
One might expect this to work if the traditional property is private in a parent class,
but I think even if the patch allowed that special case (I've not tried it), it should not.
Steve Clay
Here is the updated RFC incorporating the feedback from previous rounds of
discussion.The RFC does not specify whether it's a fatal error to define a class
(directly or via extends/traits) which has both a traditional property and
accessor with the same name, but I think this should be prohibited to avoid
confusion.
I think it is explained in the RFC:
https://wiki.php.net/rfc/propertygetsetsyntax-v1.2#shadowing but the
code example doesn't reflect that. Perhaps my understanding is flawed.
Also, I was under the impression that we wanted to remove the magic
$value
for the setter. It seems the RFC intentionally left it in
there. Any real reason why?
I am confused by one thing about the RFC. There is a section for default
accessor implementations where you specify an accessor without a body,
however many of the examples omit the isset and unset accessors. I would
assuming that omitting an accessor would provide the automagic
implementation. If this is the case what is the need for the special
syntax? If this is not the case then what is the effect of omitting an
accessor?
I do see that omitting the setter creates a read-only property, however I
think the syntax would be less ambiguous and easier to use by introducing a
readonly
keyword:
class MyClass
{
public readonly $myProp {
// ...
}
}
This would also eliminate the need for additional syntax for default
accessors. There is one problem I see with this however, what happens when
a setter is provided for a readonly property?
If this has already been discussed, please accept my apologies and maybe
provide a link to the discussion.
Regards,
Philip
Here is the updated RFC incorporating the feedback from previous rounds of
discussion.https://wiki.php.net/rfc/**propertynot getsetsyntax-v1.2https://wiki.php.net/rfc/propertygetsetsyntax-v1.2
I'm posting it for final review so I can move to voting on Jan 7th.
Please note that the current fork is not quite up-to-date with the RFC but
will be within a few more days.-Clint
--
-Clint
I am confused by one thing about the RFC. There is a section for default
accessor implementations where you specify an accessor without a body,
however many of the examples omit the isset and unset accessors. I would
assuming that omitting an accessor would provide the automagic
implementation. If this is the case what is the need for the special
syntax? If this is not the case then what is the effect of omitting an
accessor?
Omitting get/set declaration (and body) makes the property read only or
write only.
Omitting isset/unset has the same effect as declaring it without a
body. This is described in the RFC under Automatic Implementations with
this line:
Note that isset/unset implementations will always be provided if they
are not defined or if they are explicitly auto-defined (as above).
I do see that omitting the setter creates a read-only property, however I
think the syntax would be less ambiguous and easier to use by introducing a
readonly
keyword:class MyClass { public readonly $myProp { // ... } }
This would also eliminate the need for additional syntax for default
accessors. There is one problem I see with this however, what happens when
a setter is provided for a readonly property?If this has already been discussed, please accept my apologies and maybe
provide a link to the discussion.
This point of contention was discussed ad nauseam and nobody wanted the
read-only/write-only keywords, they were removed from 1.1 -> 1.2
Please see this document:
https://wiki.php.net/rfc/propertygetsetsyntax-as-implemented/change-requests
Which documents all points of contention from 1.1 and what their
resolution was.
Regards,
PhilipHere is the updated RFC incorporating the feedback from previous rounds of
discussion.https://wiki.php.net/rfc/**propertynot getsetsyntax-v1.2https://wiki.php.net/rfc/propertygetsetsyntax-v1.2
I'm posting it for final review so I can move to voting on Jan 7th.
Please note that the current fork is not quite up-to-date with the RFC but
will be within a few more days.-Clint
--
-Clint--
--
-Clint
Omitting isset/unset has the same effect as declaring it without a body. This is
described in the RFC under Automatic Implementations with this line:Note that isset/unset implementations will always be provided if they are not defined or
if they are explicitly auto-defined (as above).
I think the RFC could make this clearer: "isset & unset are always provided with the
default implementations unless the author provides his/her own."
Looking closer at the default implementations of isset/unset, I'm worried these could lead
to confusion. Consider this code:
class TimePeriod {
private $Seconds = 3600;
public $Hours {
get { return $this->Seconds / 3600; }
set { $this->Seconds = $value; }
}
}
The RFC's default implementation is always bound to the shadow property, so here's what
you really get:
class TimePeriod {
private $Seconds = 3600;
public $Hours {
get { return $this->Seconds / 3600; }
set { $this->Seconds = $value; }
// auto-generated
isset { return $this->Hours != NULL; }
unset { $this->Hours = NULL; }
}
}
Note the resulting behavior:
$t = new TimePeriod;
$t->Hours; // 1
isset($t->Hours); // false !?
unset($t->Hours);
$t->Hours; // still 1
Effectively, authors who don't base their getters/setters on the shadowed property must be
urged to create their own isset/unset because the default ones would be useless. I'm not
crazy about this.
I'd prefer these default implementations:
isset { return $this->__getHours() != NULL; }
unset { $this->__setHours(NULL); }
$t = new TimePeriod;
$t->Hours; // 1
isset($t->Hours); // true
unset($t->Hours);
$t->Hours; // null
isset($t->Hours); // false
Note these also work as expected when using the default get/set implementations. Of,
course, my implementations don't actually work because you can't call an accessor from
an accessor...
Steve Clay
Omitting isset/unset has the same effect as declaring it without a
body. This is
described in the RFC under Automatic Implementations with this line:Note that isset/unset implementations will always be provided if they
are not defined or
if they are explicitly auto-defined (as above).I think the RFC could make this clearer: "isset & unset are always
provided with the default implementations unless the author provides
his/her own."
I can do that, no problem.
Looking closer at the default implementations of isset/unset, I'm
worried these could lead to confusion. Consider this code:class TimePeriod {
private $Seconds = 3600;
public $Hours {
get { return $this->Seconds / 3600; }
set { $this->Seconds = $value; }
}
}The RFC's default implementation is always bound to the shadow
property, so here's what you really get:class TimePeriod {
private $Seconds = 3600;
public $Hours {
get { return $this->Seconds / 3600; }
set { $this->Seconds = $value; }// auto-generated isset { return $this->Hours != NULL; } unset { $this->Hours = NULL; } }
}
Note the resulting behavior:
$t = new TimePeriod;
$t->Hours; // 1
isset($t->Hours); // false !?
unset($t->Hours);
$t->Hours; // still 1Effectively, authors who don't base their getters/setters on the
shadowed property must be urged to create their own isset/unset
because the default ones would be useless. I'm not crazy about this.
Sorry, there was a typo in that RFC there, this line:
isset { return $this->Hours != NULL; }
Should have been with !==:
isset { return $this->Hours !== NULL; }
I've already updated the 1.2 doc to reflect the correct way.
Given what I mentioned above, I'm assuming you did not test this with
the fork, right? Just based your comments on how it should logically
work (with the incorrect != vs !==?)
One last thing about that, the isset/unset with $this->Hours calls the
getter to retrieve the $this->Hours value, so it behaves as your example
below indicates.
I'd prefer these default implementations:
isset { return $this->__getHours() != NULL; }
unset { $this->__setHours(NULL); }$t = new TimePeriod;
$t->Hours; // 1
isset($t->Hours); // true
unset($t->Hours);
$t->Hours; // null
isset($t->Hours); // falseNote these also work as expected when using the default get/set
implementations. Of, course, my implementations don't actually work
because you can't call an accessor from an accessor...
Steve, are you testing these w/ the fork? Sounds like you are... But
your above sentence is not accurate, you can call an accessor from an
accessor. isset { return $this->Hours !== NULL; } calls the getter for
the value and compares it with NULL.
Steve Clay
--
-Clint
Sorry, there was a typo in that RFC there, this line:
isset { return $this->Hours != NULL; }
Should have been with !==:
isset { return $this->Hours !== NULL; }I've already updated the 1.2 doc to reflect the correct way.
Given what I mentioned above, I'm assuming you did not test this with the fork, right?
Just based your comments on how it should logically work (with the incorrect != vs !==?)
I haven't tested the fork. I just borrowed your logic with the typo :)
One last thing about that, the isset/unset with $this->Hours calls the getter to retrieve
the $this->Hours value, so it behaves as your example below indicates.
The RFC says, "only the accessors themselves may directly access the shadowed property." I
read that as:
Within get, $this->Hours is the raw shadowed property.
Within set, $this->Hours is the raw shadowed property.
Within isset, $this->Hours is the raw shadowed property.
Within unset, $this->Hours is the raw shadowed property.
But you seem to imply:
Within get, $this->Hours is the raw shadowed property.
Within set, $this->Hours is the raw shadowed property.
Within isset, $this->Hours is accessed via __getHours()/__setHours().
Within unset, $this->Hours is accessed via __getHours()/__setHours().
So really the default implementations behave like this:
isset { return $this->__getHours() !== NULL; }
unset { $this->__setHours(NULL); }
I think the RFC should be much clearer about what property access actually means within
each accessor method, as I expect users to be very surprised by this behavior.
This is also looks like it could lead to surprises:
Within get, $this->Hours is the raw shadowed property.
Within get, parent::$Hours is accessed via parent::__getHours()/parent::__setHours().
Also, is there no way to access the shadow property within isset/unset? If not, is there a
good reason to not allow it?
Also, do/should multiple property accessors interact? Consider:
class Foo {
public $a {
get { $this->a = 1; return 2; }
}
public $b {
get { return $this->a; }
}
}
$foo = new Foo;
$foo->a; // 2 (but shadowed property is 1)
$foo->b; // 1 or 2?
Steve Clay
All great questions Steve, doesn't quite work the way you have here.
Specifically each get/set/isset/unset have their own guards (just like
__get(), __set(), __isset() and __unset()) which means that:
Within get: $this->Hours can read the underlying property but not write
to it, if it attempts to write, that write would go through the setter.
Within set: $this->Hours = 1 can write to the underlying property but a
read of the property would go through the getter.
Within isset/unset: the same rules apply, a read goes through the getter
and a write goes through the setter.
I've updated the Shadowing section of the RFC which I hope clears this
up, it also includes a slightly modified version of your example at the
bottom with comments.
More comments below:
Sorry, there was a typo in that RFC there, this line:
isset { return $this->Hours != NULL; }
Should have been with !==:
isset { return $this->Hours !== NULL; }I've already updated the 1.2 doc to reflect the correct way.
Given what I mentioned above, I'm assuming you did not test this with
the fork, right?
Just based your comments on how it should logically work (with the
incorrect != vs !==?)I haven't tested the fork. I just borrowed your logic with the typo :)
One last thing about that, the isset/unset with $this->Hours calls
the getter to retrieve
the $this->Hours value, so it behaves as your example below indicates.The RFC says, "only the accessors themselves may directly access the
shadowed property." I read that as:Within get, $this->Hours is the raw shadowed property.
Within set, $this->Hours is the raw shadowed property.
Within isset, $this->Hours is the raw shadowed property.
Within unset, $this->Hours is the raw shadowed property.
But you seem to imply:Within get, $this->Hours is the raw shadowed property.
Within set, $this->Hours is the raw shadowed property.
Within isset, $this->Hours is accessed via __getHours()/__setHours().
Within unset, $this->Hours is accessed via __getHours()/__setHours().So really the default implementations behave like this:
isset { return $this->__getHours() !== NULL; }
unset { $this->__setHours(NULL); }
Technically this is an accurate translation of what happens with the RFC
example, but this would work as well.I think the RFC should be much clearer about what property access
actually means within each accessor method, as I expect users to be
very surprised by this behavior.This is also looks like it could lead to surprises:
Within get, $this->Hours is the raw shadowed property.
Within get, parent::$Hours is accessed via
parent::__getHours()/parent::__setHours().
I'm not sure I understand what you mean here... within get the parent
accessor is accessed via parent::$Hours, internally that is translated
to what you have above but none of this parent::__getHours() needs to be
typed out, parent::$Hours will suffice.Also, is there no way to access the shadow property within
isset/unset? If not, is there a good reason to not allow it?
Yes, it would bypass the getter and setter which may be dynamic and
never set the underlying property.
Also, do/should multiple property accessors interact? Consider:
class Foo {
public $a {
get { $this->a = 1; return 2; }
}
public $b {
get { return $this->a; }
}
}$foo = new Foo;
$foo->a; // 2 (but shadowed property is 1)
$foo->b; // 1 or 2?
This would cause a "Warning, unable to set property Foo::$a, no setter
defined." Both of your $foo->a and $foo->b lines would return the return
value of the Foo::$a getter which is always 2.
The reason it would produce that warning is because you do not have a
setter for $a defined and therefore it is read only, even to its-self.
Only the setter may set the underlying value.
Steve Clay
--
-Clint
I've updated the Shadowing section of the RFC which I hope clears this up, it also includes a slightly modified version of your example at the bottom with comments.
Updated RFC really helps. The notion of $this->prop access semantics depending on which accessor you're in seems important for the RFC as I think it will seem foreign to a lot of devs.
When I make traditional PHP "accessor" methods, I have complete control; if I want getFoo() to set private $foo without calling setFoo(), I can. Not so with real accessors. Probably a good thing :)
One more concern, sorry if it was covered already: will case-insensitivity of methods mean you can't define getters for both $foo and $Foo?
Steve
That has not been covered and it is a problem, just tested it.
Anyone have any preferences on a resolution?
Only thing that really needs to occur is that the function names need to
be unique, we could prefix any capitals in a variable name with an
additional _ such that:
class A {
public $Foo { get; set; }
public $foo { get; set; }
}
Would mean there would be these function names:
A::__get_Foo();
A::__set_Foo();
A::__getfoo();
A::__setfoo();
Along w/ their unset/isset'rs.
This is what happens as of right now:
Fatal error: Cannot redeclare a::__getfoo() in
/opt/php-core/git/trunk-accessor/cpriest/php/quick2.php on line 9
Good catch on that one.
I've updated the Shadowing section of the RFC which I hope clears this up, it also includes a slightly modified version of your example at the bottom with comments.
Updated RFC really helps. The notion of $this->prop access semantics depending on which accessor you're in seems important for the RFC as I think it will seem foreign to a lot of devs.When I make traditional PHP "accessor" methods, I have complete control; if I want getFoo() to set private $foo without calling setFoo(), I can. Not so with real accessors. Probably a good thing :)
One more concern, sorry if it was covered already: will case-insensitivity of methods mean you can't define getters for both $foo and $Foo?
Steve
--
-Clint
Hi!
Within get: $this->Hours can read the underlying property but not write
to it, if it attempts to write, that write would go through the setter.
Within set: $this->Hours = 1 can write to the underlying property but a
read of the property would go through the getter.
Are the accesses also applying to called functions/accessors? I.e.
consider this:
class SuperDate {
private $date {
get;
set(DateTime $x) { $this->date = $x; $this->timestamp =
$x->getTimestamp();
}
private $timestamp {
get;
set($t) { $t = (int)$t; $this->timestamp = $t; $this->date = new
DateTime("@$t"); }
}
}
What happens to it? Would it get into infinite loop or will just set the
value twice? What would be the correct way to write such a code (note
the real code of course could be much more complicated and probably
involve dozen of properties with complex dependencies between them).
Also, if this applies to functions called from getter/setter (which
seems to be the case from the code, unless I miss something), consider this:
class UserContext {
protected $user;
public $logger;
public $username {
get() { $this->logger->log("Getting username"); return $user->name; }
set($n) { $this->user = User::get_by_name($n); }
}
}
class Logger {
protected $ctx;
public function __construct(UserContext $ctx) {
$this->ctx = $ctx;
$this->logfile = fopen("/tmp/log", "a+");
}
public function log($message) {
fwrite($this->logfile, "[$this->ctx->username] $message\n");
}
}
$u = new UserContext();
$u->logger = new Logger($u);
$u->username = "johndoe";
echo $u->username;
What would happen with this code? Will the log be able to log the actual
user name, and if not, how you protect from such thing? $username is a
part of public API of UserContext, so whoever is writing Logger has
right to use it. On the other hand, whoever is using logger->log in
UserContext has absolutely no way to know that Logger is using
ctx->username internally, as these components can change completely
independently and don't know anything about each other besides public APIs.
What I am getting at here is that shadowing seems to create very tricky
hidden state that can lead to very bad error situations when using
public APIs without knowledge of internal implementation.
Within isset/unset: the same rules apply, a read goes through the getter
and a write goes through the setter.
With this code:
class Foo {
public $bar {
get;
set;
}
}
How could I make it set to 2 by default and isset() return true when I
instantiate the class? Currently, I see no way to assign default values
for properties. Is it planned?
Stanislav Malyshev, Software Architect
SugarCRM: http://www.sugarcrm.com/
(408)454-6900 ext. 227
Hi!
Within get: $this->Hours can read the underlying property but not write
to it, if it attempts to write, that write would go through the setter.
Within set: $this->Hours = 1 can write to the underlying property but a
read of the property would go through the getter.
Are the accesses also applying to called functions/accessors? I.e.
consider this:class SuperDate {
private $date {
get;
set(DateTime $x) { $this->date = $x; $this->timestamp =
$x->getTimestamp();
}
private $timestamp {
get;
set($t) { $t = (int)$t; $this->timestamp = $t; $this->date = new
DateTime("@$t"); }
}
}What happens to it? Would it get into infinite loop or will just set the
value twice? What would be the correct way to write such a code (note
the real code of course could be much more complicated and probably
involve dozen of properties with complex dependencies between them).
This recursion is protected in the same way that the code above would be
protected using __get/__set in that the first set::$date locks the
setter, so technically in this case the 2nd call to $this->date = new
DateTime(...); would directly access the underlying date property.
I don't like this personally (because now the $timestamp setter is
directly accessing the underlying $date variable) but this is precisely
what would happen if this same code were implemented with __get() and
__set().
Also, if this applies to functions called from getter/setter (which
seems to be the case from the code, unless I miss something), consider this:class UserContext {
protected $user;
public $logger;
public $username {
get() { $this->logger->log("Getting username"); return $user->name; }
set($n) { $this->user = User::get_by_name($n); }
}
}class Logger {
protected $ctx;
public function __construct(UserContext $ctx) {
$this->ctx = $ctx;
$this->logfile = fopen("/tmp/log", "a+");
}
public function log($message) {
fwrite($this->logfile, "[$this->ctx->username] $message\n");
}
}$u = new UserContext();
$u->logger = new Logger($u);
$u->username = "johndoe";
echo $u->username;What would happen with this code? Will the log be able to log the actual
user name, and if not, how you protect from such thing? $username is a
part of public API of UserContext, so whoever is writing Logger has
right to use it. On the other hand, whoever is using logger->log in
UserContext has absolutely no way to know that Logger is using
ctx->username internally, as these components can change completely
independently and don't know anything about each other besides public APIs.
What I am getting at here is that shadowing seems to create very tricky
hidden state that can lead to very bad error situations when using
public APIs without knowledge of internal implementation.
Again this is creating recursion and the same rules that apply to
__get()/__set() apply here, yourfwrite()
access of
UserContext::$username when called through the getter for $username is
already locked and thus thefwrite()
line directly accesses the
underlying property.
Same as before, this is exactly the same behavior you would get with
__get() and __set(). These guard mechanisms were put in place with a
revision to __get()/__set() shortly after they were first released
(first release didn't allow recursion at all).
We could possibly also catch this scenario and either show a warning on
the fwrite()
direct access or a fatal error or just allow the direct
access. Since __get() and __set() already work this way, it's probably
fine as-is, even if not perfect OO.
Anyone know what would happen in such a case with another language that
supports accessors? My guess would be infinite recursion... (no guards)...
Within isset/unset: the same rules apply, a read goes through the getter
and a write goes through the setter.
With this code:class Foo {
public $bar {
get;
set;
}
}How could I make it set to 2 by default and isset() return true when I
instantiate the class? Currently, I see no way to assign default values
for properties. Is it planned?
In the changing over to accessors being distinct from properties into
"properties with accessors" I had considered this and think it would be
great. Unless I had some trouble with the lexer it should be trivial to
add, would be something like this:
class Foo {
public $bar = 2 {
get;
set;
}
}
Anyone object to this addition to the spec?
--
-Clint
class Foo {
public $bar = 2 {
get;
set;
}
}
Consider properties not based on shadowed values:
class Foo {
private $realbar;
public $bar = 2 {
get { return $this->realbar; }
set { $this->realbar = $value; }
}
}
Here, initializing the shadow property is useless. It's similar to the case where you want
to have the initial value of a traditional property be the result of an expression (and
you can't because it would create a chicken-egg problem during construction).
Option 1: The most powerful solution I see is to have an init function that's implicitly
called before first access (and with direct write access to the shadow property if
needed). Consider trying to emulate the crazy document.cookie API:
class Document {
private $cookieProps;
public $cookie {
get { return /* based on cookieProps */; }
set { /* set some cookieProps */ }
// called implicitly before the first access (not before the constructor runs)
// and would have direct access to the shadow property.
init { /* set up cookieProps */ }
}
}
Pros: can run any code necessary to initialize the property
Cons: must make a function just to have a default
Option 2: Keep the traditional syntax, but instead of always setting the shadow property,
call the setter with the given initial value directly before the first access.
Pros: familiar syntax; more expected behavior in some cases
Cons: what if there's no setter?; cannot run arbitrary setup code; setter can't
distinguish between a "user" set and the implicitly set before first access.
Option 3: A mix of both. The shadow property is always initialized with the value from the
traditional syntax. If needed, you can have an init that's called before first access.
Pros: For simple shadowed properties, just works as you'd expect and with familiar syntax;
Still allows robust initialization before first access if needed.
Cons: Author must remember that traditional syntax only useful for shadow prop.
Steve Clay
class Foo {
public $bar = 2 {
get;
set;
}
}Consider properties not based on shadowed values:
class Foo {
private $realbar;public $bar = 2 { get { return $this->realbar; } set { $this->realbar = $value; } }
}
What would be the point of this? Why not just do this:
class Foo {
private $realbar = 2;
public $bar {
get { return $this->realbar; }
set { $this->realbar = $value; }
}
}
Here, initializing the shadow property is useless. It's similar to the
case where you want to have the initial value of a traditional
property be the result of an expression (and you can't because it
would create a chicken-egg problem during construction).Option 1: The most powerful solution I see is to have an init function
that's implicitly called before first access (and with direct write
access to the shadow property if needed). Consider trying to emulate
the crazy document.cookie API:class Document {
private $cookieProps;public $cookie { get { return /* based on cookieProps */; } set { /* set some cookieProps */ } // called implicitly before the first access (not before the
constructor runs)
// and would have direct access to the shadow property.
init { /* set up cookieProps */ }
}
}Pros: can run any code necessary to initialize the property
Cons: must make a function just to have a defaultOption 2: Keep the traditional syntax, but instead of always setting
the shadow property, call the setter with the given initial value
directly before the first access.Pros: familiar syntax; more expected behavior in some cases
Cons: what if there's no setter?; cannot run arbitrary setup code;
setter can't distinguish between a "user" set and the implicitly set
before first access.Option 3: A mix of both. The shadow property is always initialized
with the value from the traditional syntax. If needed, you can have an
init that's called before first access.Pros: For simple shadowed properties, just works as you'd expect and
with familiar syntax; Still allows robust initialization before first
access if needed.
Cons: Author must remember that traditional syntax only useful for
shadow prop.
I like the idea of an init function, but that would have to wait for a
further release I think, or delay this whole project a year.
Adding the ability to specify an = X value to a property with accessors
would be trivial.
Steve Clay
--
-Clint
I like the idea of an init function, but that would have to wait for a
further release I think, or delay this whole project a year.
We have constructors, shouldn't those be sufficient for that task?
--
Regards,
Mike
I like the idea of an init function, but that would have to wait for a
further release I think, or delay this whole project a year.We have constructors, shouldn't those be sufficient for that task?
My initial view was that property inits would encourage encapsulation of property setup
logic, and make sure the property was ready before access in the constructor. Basically
the same reasons we allow initializing regular properties now.
But now I think it would embolden authors to make overly complex properties (like
document.cookie) that should really be separate objects. Lets not do that.
Steve Clay
class Foo {
private $realbar;public $bar = 2 { get { return $this->realbar; } set { $this->realbar = $value; } }
}
What would be the point of this?
I think it would be more readable for someone using the class. As a user it helps to know
the default of the property I'll actually be interacting with. In a non-trivial getter the
default may be much harder to tease out.
But yes, I agree that it's straightforward enough for the class author to set defaults as
needed, so I see no problem moving forward. The RFC should make clear that only the shadow
value is set (a setter, if present, is not used).
In the features docs I suggest finding a term for a non-traditional property, like
"guarded property".
Also I think you should eliminate all "shadowing" language. IMO it adds more confusion
than it removes. Here's some language that may/may not be useful:
Almost all interaction with a guarded property is "proxied", meaning the getter and setter
methods are used instead of direct reading/writing of the value. There are only two
exceptions:
-
Within a getter's scope, reads are not proxied: the property is read directly.
-
Within a setter's scope, writes are not proxied; the property is written to directly.
Accessors are free to interact with other properties which are visible to them, but access
to other guarded properties is always proxied.
Accessors are not required to use the property value, but it always exists.
Steve Clay
Clint,
...snip...
I like the idea of an init function, but that would have to wait for a
further release I think, or delay this whole project a year.
Well, just speaking in general, we should not try to rush through these
kinds of design decisions. They should only be done incrementally if it
makes sense to do it incrementally. That really needs to be thought out.
IMHO there's no problem delaying anything a year. We're in yearly releases
now, so it's not like the 5.1 and 5.2 days where the "next release it may
get in" was literally 5 or 10 years off... I'd much rather see it done
right, even if later, rather than rushed in and sticking us with the
implementation forever...
Just pointing it out (in case it needed to be said)...
Anthony
class SuperDate {
private $date {
get;
set(DateTime $x) { $this->date = $x; $this->timestamp =
$x->getTimestamp();
}
private $timestamp {
get;
set($t) { $t = (int)$t; $this->timestamp = $t; $this->date = new
DateTime("@$t"); }
}
}What happens to it? Would it get into infinite loop or will just set the
value twice? What would be the correct way to write such a code (note
I think infinite recursion is a potential issue for lots of logging setups ("let's log
when someone calls the logger!") and situations where you have multiple values to keep in
sync. The accessor implementation shouldn't try to solve these design problems.
class UserContext {
protected $user;
public $logger;
public $username {
get() { $this->logger->log("Getting username"); return $user->name; }
set($n) { $this->user = User::get_by_name($n); }
}
}class Logger {
protected $ctx;
public function __construct(UserContext $ctx) {
$this->ctx = $ctx;
$this->logfile = fopen("/tmp/log", "a+");
}
public function log($message) {
fwrite($this->logfile, "[$this->ctx->username] $message\n");
}
}$u = new UserContext();
$u->logger = new Logger($u);
$u->username = "johndoe";
echo $u->username;What would happen with this code? Will the log be able to log the actual
user name, and if not, how you protect from such thing? $username is a
part of public API of UserContext, so whoever is writing Logger has
right to use it. On the other hand, whoever is using logger->log in
UserContext has absolutely no way to know that Logger is using
ctx->username internally, as these components can change completely
independently and don't know anything about each other besides public APIs.
What I am getting at here is that shadowing seems to create very tricky
hidden state that can lead to very bad error situations when using
public APIs without knowledge of internal implementation.
Again, the problem is not shadowing (not even in use here) but really general information
hiding. You can create these problems anytime you have hidden information and
interdependent objects, and it's an API design problem.
Steve Clay
class SuperDate {
private $date {
get;
set(DateTime $x) { $this->date = $x; $this->timestamp =
$x->getTimestamp();
}
private $timestamp {
get;
set($t) { $t = (int)$t; $this->timestamp = $t; $this->date = new
DateTime("@$t"); }
}
}What happens to it? Would it get into infinite loop or will just set the
value twice? What would be the correct way to write such a code (noteI think infinite recursion is a potential issue for lots of logging
setups ("let's log when someone calls the logger!") and situations
where you have multiple values to keep in sync. The accessor
implementation shouldn't try to solve these design problems.
__get()/__set() already implement infinite recursion guards, Accessors
are just doing the same thing.
class UserContext {
protected $user;
public $logger;
public $username {
get() { $this->logger->log("Getting username"); return
$user->name; }
set($n) { $this->user = User::get_by_name($n); }
}
}class Logger {
protected $ctx;
public function __construct(UserContext $ctx) {
$this->ctx = $ctx;
$this->logfile = fopen("/tmp/log", "a+");
}
public function log($message) {
fwrite($this->logfile, "[$this->ctx->username] $message\n");
}
}$u = new UserContext();
$u->logger = new Logger($u);
$u->username = "johndoe";
echo $u->username;What would happen with this code? Will the log be able to log the actual
user name, and if not, how you protect from such thing? $username is a
part of public API of UserContext, so whoever is writing Logger has
right to use it. On the other hand, whoever is using logger->log in
UserContext has absolutely no way to know that Logger is using
ctx->username internally, as these components can change completely
independently and don't know anything about each other besides public
APIs.
What I am getting at here is that shadowing seems to create very tricky
hidden state that can lead to very bad error situations when using
public APIs without knowledge of internal implementation.Again, the problem is not shadowing (not even in use here) but really
general information hiding. You can create these problems anytime you
have hidden information and interdependent objects, and it's an API
design problem.Steve Clay
--
-Clint
Hi!
I think infinite recursion is a potential issue for lots of logging setups ("let's log
when someone calls the logger!") and situations where you have multiple values to keep in
sync. The accessor implementation shouldn't try to solve these design problems.
The whole problem here is that the only reason why it is a problem is
because of the accessors that have hidden state in guards. If it were
regular variables (and for all the API consumer knows, they are) there
wouldn't be any question about if we're allowed to read $user->username
or not - if it's public, of course we can read it. So the problem exists
because the hidden state exists. It's not the problem of the code that
uses public APIs in completely legal way, it is the problem of our
implementation that we have hidden state that changes how variable
access works. With __get we mostly ignored it since recursion blocker
leads to the same result as if the variable did not exist - which is the
case for __get anyway, kind of - so it was less explicit. With accessors
it may become more painful.
Stanislav Malyshev, Software Architect
SugarCRM: http://www.sugarcrm.com/
(408)454-6900 ext. 227
Not really sure what to say about this, we can either guard against
recursion or not, I see
no reason not to guard against recursion except that it could allow
unauthorized direct
access when the guard is active.
I guess a third option could be that if the property is attempted to be
accessed while
being guarded and that access is not from within the accessor, then it
issues a warning (or not)
and returns NULL.
Thoughts?
Hi!
I think infinite recursion is a potential issue for lots of logging setups ("let's log
when someone calls the logger!") and situations where you have multiple values to keep in
sync. The accessor implementation shouldn't try to solve these design problems.
The whole problem here is that the only reason why it is a problem is
because of the accessors that have hidden state in guards. If it were
regular variables (and for all the API consumer knows, they are) there
wouldn't be any question about if we're allowed to read $user->username
or not - if it's public, of course we can read it. So the problem exists
because the hidden state exists. It's not the problem of the code that
uses public APIs in completely legal way, it is the problem of our
implementation that we have hidden state that changes how variable
access works. With __get we mostly ignored it since recursion blocker
leads to the same result as if the variable did not exist - which is the
case for __get anyway, kind of - so it was less explicit. With accessors
it may become more painful.
--
-Clint
The whole problem here is that the only reason why it is a problem is
because of the accessors that have hidden state in guards. If it were
regular variables (and for all the API consumer knows, they are) there
Please ignore this if it's been debated before:
AFAICT C# strictly separates fields ("properties" in PHP) and properties (a set of
accessors that emulate a field).
So the RFC provides 3 features (using C# terms):
- A property API
- A built-in storage variable so you don't need a separate field
- Access to the storage variable as if it were a field of the same name
I think #2 is useful, avoiding the need to make a separate field just to make properties
read-only or type-hinted. However I think the complexity and confusion we're running into
is mostly caused by #3.
I think we might be better served by having another way to access this storage variable.
What if instead, we have the storage var available as $prop inside all the accessors?
These would be the default implementations:
get { return $prop; }
set($value) { $prop = $value; }
isset { return $prop !== NULL; }
unset { $prop = NULL; }
Pros:
- Makes clear that $prop is regular var access, and that $this->PropertyName always goes
through accessors - Gives isset/unset full access to the storage var, which allows doing things that can't
be done via setter/getter. E.g. you could actually implement a property being "unset",
which would be different from having it set to NULL.
Cons:
- Allows "evil", like having reads affect the storage var.
- Allows authors to hang themselves with recursive accessor calls, BUT those mistakes
would be apparent from looking at the code.
What functionality possible in the RFC would be lost by this?
Steve Clay
I like the alternate idea here, but I'm not sure what advantage it has
over the current situation?
This line of reasoning revealed a difference between what your verbiage
suggestion from a few days ago suggests and what is true.
With the "guards" it's pretty simple what occurs. If an unset
(accessor) is called, and another unset is called on that property, the
same internal function which handles unset is called. In the 2nd
instance, it see's that the unset is already being guarded and so the
ordinary "unset" functionality is invoked.
Therefore, take a look at this gist: https://gist.github.com/4463107
I will need to update the RFC because your previous summary which I took
verbatim I missed being incorrect. It's really, in my book, a lot
simpler to say that if an accessor is already "in the call chain" then
it will not be called again and "standard property rules" take effect.
This is why a getter can get its value directly. It's also why if that
getter calls some other code which tries to "get" the value, that get
also bypasses the getter (which prevents infinite loops).
-Clint
The whole problem here is that the only reason why it is a problem is
because of the accessors that have hidden state in guards. If it were
regular variables (and for all the API consumer knows, they are) therePlease ignore this if it's been debated before:
AFAICT C# strictly separates fields ("properties" in PHP) and
properties (a set of accessors that emulate a field).So the RFC provides 3 features (using C# terms):
- A property API
- A built-in storage variable so you don't need a separate field
- Access to the storage variable as if it were a field of the same name
I think #2 is useful, avoiding the need to make a separate field just
to make properties read-only or type-hinted. However I think the
complexity and confusion we're running into is mostly caused by #3.I think we might be better served by having another way to access this
storage variable.What if instead, we have the storage var available as $prop inside all
the accessors? These would be the default implementations:get { return $prop; }
set($value) { $prop = $value; }
isset { return $prop !== NULL; }
unset { $prop = NULL; }Pros:
- Makes clear that $prop is regular var access, and that
$this->PropertyName always goes through accessors- Gives isset/unset full access to the storage var, which allows doing
things that can't be done via setter/getter. E.g. you could actually
implement a property being "unset", which would be different from
having it set to NULL.Cons:
- Allows "evil", like having reads affect the storage var.
- Allows authors to hang themselves with recursive accessor calls, BUT
those mistakes would be apparent from looking at the code.What functionality possible in the RFC would be lost by this?
Steve Clay
--
-Clint
I like the alternate idea here, but I'm not sure what advantage it has over the current
situation?
See the "Pros" I listed. The primary being a clear differentiation between calling
accessors and handling of the storage value associated with the property.
if an accessor is
already "in the call chain" then it will not be called again and "standard property rules"
take effect.
I see that as not simpler, and in fact leading to small bugs whenever getters/setters
start becoming complex enough to call outside methods. Consider this:
class Foo {
public $bar = 2 {
get { return $this->calcBar(); }
}
public function calcBar() {
return $this->bar * 3;
}
}
echo $foo->bar; // 6
Within calcBar, "$this->bar" is the raw property (2), because hidden state has "unwrapped"
(if you will) the property.
echo $foo->calcBar(); // 18
But here, within the first call of calcBar "$this->bar" triggers the getter. Now, of
course, this is a foolish implementation, but within any method that could be called from
the getter/setter, the symbol $this->bar could mean two completely different things; I
think this is bad.
If, as I proposed, the storage var were only accessible as $prop in the accessor scopes,
that would force authors to pass $prop to any supporting methods, clarifying intent.
$this->bar would always be accessor calls.
In this model, I think infinite-loop-causing recursions would be easier to spot. If
absolutely necessary we could always throw a fatal error whenever a getter was called
twice in the same call chain.
Steve
AFAICT C# strictly separates fields ("properties" in PHP) and properties (a set of
accessors that emulate a field).So the RFC provides 3 features (using C# terms):
- A property API
- A built-in storage variable so you don't need a separate field
- Access to the storage variable as if it were a field of the same name
I think #2 is useful, avoiding the need to make a separate field just to make properties
read-only or type-hinted. However I think the complexity and confusion we're running
into is mostly caused by #3.I think we might be better served by having another way to access this storage variable.
What if instead, we have the storage var available as $prop inside all the accessors?
These would be the default implementations:get { return $prop; }
set($value) { $prop = $value; }
isset { return $prop !== NULL; }
unset { $prop = NULL; }Pros:
- Makes clear that $prop is regular var access, and that $this->PropertyName always
goes through accessors- Gives isset/unset full access to the storage var, which allows doing things that can't
be done via setter/getter. E.g. you could actually implement a property being "unset",
which would be different from having it set to NULL.Cons:
- Allows "evil", like having reads affect the storage var.
- Allows authors to hang themselves with recursive accessor calls, BUT those mistakes
would be apparent from looking at the code.What functionality possible in the RFC would be lost by this?
Steve Clay
Steve Clay
The problem I see with your proposal is that get/isset/unset could all
bypass the setter which I think is a bad idea.
If the problem is simply this "hidden state" that keeps being referred
to, I thought the solution I mentioned before would be better.
To re-iterate that prior idea is to make it behave identically to __get().
The first call to a getter would initiate the (calling) guard, any other
access outside of the getter from that point forward would return NULL,
but the access by the getter would return the property.
It would stop the infinite loop, stop invalid access, the author could
see where they've gone wrong (a warning could be emitted even) and we
maintain the current solution without introducing more complexity (which
would probably lead to further problems).
For your pro's listed
- I don't think a local variable named $prop (for example) is very clear
that it's going to modify a property of $this, I would expect
$this->prop and that $prop would be modifying a local value. - That's a misconception that I accidentally let perpetuate because I
wasn't careful when I confirmed your alternate verbiage.
And then it adds the evil's listed.
-Clint
I like the alternate idea here, but I'm not sure what advantage it
has over the current
situation?See the "Pros" I listed. The primary being a clear differentiation
between calling accessors and handling of the storage value associated
with the property.if an accessor is
already "in the call chain" then it will not be called again and
"standard property rules"
take effect.I see that as not simpler, and in fact leading to small bugs whenever
getters/setters start becoming complex enough to call outside methods.
Consider this:class Foo {
public $bar = 2 {
get { return $this->calcBar(); }
}
public function calcBar() {
return $this->bar * 3;
}
}echo $foo->bar; // 6
Within calcBar, "$this->bar" is the raw property (2), because hidden
state has "unwrapped" (if you will) the property.echo $foo->calcBar(); // 18
But here, within the first call of calcBar "$this->bar" triggers the
getter. Now, of course, this is a foolish implementation, but within
any method that could be called from the getter/setter, the symbol
$this->bar could mean two completely different things; I think this is
bad.If, as I proposed, the storage var were only accessible as $prop in
the accessor scopes, that would force authors to pass $prop to any
supporting methods, clarifying intent. $this->bar would always be
accessor calls.In this model, I think infinite-loop-causing recursions would be
easier to spot. If absolutely necessary we could always throw a fatal
error whenever a getter was called twice in the same call chain.Steve
AFAICT C# strictly separates fields ("properties" in PHP) and
properties (a set of
accessors that emulate a field).So the RFC provides 3 features (using C# terms):
- A property API
- A built-in storage variable so you don't need a separate field
- Access to the storage variable as if it were a field of the same
nameI think #2 is useful, avoiding the need to make a separate field
just to make properties
read-only or type-hinted. However I think the complexity and
confusion we're running
into is mostly caused by #3.I think we might be better served by having another way to access
this storage variable.What if instead, we have the storage var available as $prop inside
all the accessors?
These would be the default implementations:get { return $prop; }
set($value) { $prop = $value; }
isset { return $prop !== NULL; }
unset { $prop = NULL; }Pros:
- Makes clear that $prop is regular var access, and that
$this->PropertyName always
goes through accessors- Gives isset/unset full access to the storage var, which allows
doing things that can't
be done via setter/getter. E.g. you could actually implement a
property being "unset",
which would be different from having it set to NULL.Cons:
- Allows "evil", like having reads affect the storage var.
- Allows authors to hang themselves with recursive accessor calls,
BUT those mistakes
would be apparent from looking at the code.What functionality possible in the RFC would be lost by this?
Steve Clay
Steve Clay
--
-Clint
Hi!
The first call to a getter would initiate the (calling) guard, any other
access outside of the getter from that point forward would return NULL,
but the access by the getter would return the property.
How we implement that? Would we have each variable access check whether
we are in the getter or not? We don't want to slow down regular property
accesses...
It would stop the infinite loop, stop invalid access, the author could
see where they've gone wrong (a warning could be emitted even) and we
Yes, I think we want to produce a notice or even warning there if we
return NULL
as this is clearly a bug in the code and should not happen
in proper code.
--
Stanislav Malyshev, Software Architect
SugarCRM: http://www.sugarcrm.com/
(408)454-6900 ext. 227
Note, there has been a show stopper of an issue with the current RFC
that many minds have been talking about how to solve.
The problem is with parent::$foo accessing the parent accessor.
In summary, the difficulty is that this is parsed as a static property
access and during compilation there is no guarantee that the parent
class structure can be accessed (to confirm/deny that it's really an
accessor and not a static property reference). This guarantee is not
possible because you can define the parent class after the subclass.
So... on to solutions that have been discussed and shot down.
-
Change the zend opcodes for static property references to identify
the true state at run-time. This would be a "hack" to be certain and
has been shot-down for that exact reason. This is also the reason that
static accessors has been abandoned for this first iteration of the
feature, because static properties are handled entirely differently from
object based properties. -
Rewrite the way static property references work, such that they make
calls into zend_object_handler.c, which is probably something that
should be done anyways as a separate RFC and project, at which point
this problem would probably be trivial to solve. -
Introduce a new opcode which could somehow solve this problem by
introducing an additional step to determine whether its a parent
accessor reference or a static property reference. This would also be
"hackish" because the proper solution is #2 and beyond the scope of this
RFC.
A recent suggestion from Stas is to use parent->$foo (note the use of ->
rather than ::)
Pros:
- It should not have any problems being implemented
- It would move this RFC to completion
Cons:
- It does create an inconsistency in that nowhere else (that I know
of) does parent->$foo do something. It may also sound like it should
work because parent::__construct() works just fine, but that works fine
because it goes through a different opcode system than static accessors do.
Really, other than doing something like the above, the only other way
would be for a getter to access it's parent by calling the function
directly, such as parent::__getFoo() but this is undesirable for a
number of reasons:
1) It forces the user to access the parent property accessor in a
different way than is used everywhere else
2) It forces the user to utilize an underlying implementation detail
that is not guaranteed to stay the same (it may change)
3) It's certainly not as syntactically nice as parent->$foo would be.
As a final alternative to this problem, we could simply not allow a
parents accessor to be used, which I think is probably the worst choice
of all.
Thoughts?
There is a github ticket discussing this issue as well here if you'd
like to chime in there on some more technical conversation about this
issue: https://github.com/cpriest/php-src/issues/8
If anyone can think of any other way to solve this such that
parent::$foo could work, I'm all ears but I'm out of ideas on this one
and I think Stas's idea is just fine.
Here is the updated RFC incorporating the feedback from previous
rounds of discussion.https://wiki.php.net/rfc/propertygetsetsyntax-v1.2
I'm posting it for final review so I can move to voting on Jan 7th.
Please note that the current fork is not quite up-to-date with the RFC
but will be within a few more days.-Clint
--
-Clint
Hi!
A recent suggestion from Stas is to use parent->$foo (note the use of ->
rather than ::)
I actually proposed parent->foo. parent->$foo implies the name of the
variable is "$foo", not "foo" - just as in $this->$foo. Yes, I know it
does not match parent::$foo - but I can't do much about it. In any case,
better not to add another inconsistency to the list of existing ones.
--
Stanislav Malyshev, Software Architect
SugarCRM: http://www.sugarcrm.com/
(408)454-6900 ext. 227
Missed that bit... I think that would add two bits of inconsistency though... (Without the $)
-Clint
Hi!
A recent suggestion from Stas is to use parent->$foo (note the use of ->
rather than ::)I actually proposed parent->foo. parent->$foo implies the name of the
variable is "$foo", not "foo" - just as in $this->$foo. Yes, I know it
does not match parent::$foo - but I can't do much about it. In any case,
better not to add another inconsistency to the list of existing ones.--
Stanislav Malyshev, Software Architect
SugarCRM: http://www.sugarcrm.com/
(408)454-6900 ext. 227
Speaking of which, parent::foo ( with :: but no $) might work as well, almost any character change could work...
parent:::$foo
parent:$foo
parent->$foo
parent->foo
parent.$foo
parent.foo
I favor having the $ in some solution though...
-Clint
Missed that bit... I think that would add two bits of inconsistency though... (Without the $)
-Clint
Hi!
A recent suggestion from Stas is to use parent->$foo (note the use of ->
rather than ::)I actually proposed parent->foo. parent->$foo implies the name of the
variable is "$foo", not "foo" - just as in $this->$foo. Yes, I know it
does not match parent::$foo - but I can't do much about it. In any case,
better not to add another inconsistency to the list of existing ones.--
Stanislav Malyshev, Software Architect
SugarCRM: http://www.sugarcrm.com/
(408)454-6900 ext. 227
Uhm.. brain fart.
I was thinking $this->$foo was normal when I wrote this up, I would
change my last statement from the earlier email to any syntax which did
not include a $.
That being said then, I think I favor parent->foo the best.
One other possible alternative would be to treat parent "like a variable..."
$parent->foo
Speaking of which, parent::foo ( with :: but no $) might work as well, almost any character change could work...
parent:::$foo
parent:$foo
parent->$foo
parent->foo
parent.$foo
parent.fooI favor having the $ in some solution though...
-Clint
Missed that bit... I think that would add two bits of inconsistency though... (Without the $)
-Clint
Hi!
A recent suggestion from Stas is to use parent->$foo (note the use of ->
rather than ::)
I actually proposed parent->foo. parent->$foo implies the name of the
variable is "$foo", not "foo" - just as in $this->$foo. Yes, I know it
does not match parent::$foo - but I can't do much about it. In any case,
better not to add another inconsistency to the list of existing ones.--
Stanislav Malyshev, Software Architect
SugarCRM: http://www.sugarcrm.com/
(408)454-6900 ext. 227
--
--
-Clint
Hi Clint,
got it.
Am 04.01.2013 um 16:28 schrieb Clint Priest cpriest@zerocue.com:
Uhm.. brain fart.
I was thinking $this->$foo was normal when I wrote this up, I would change my last statement from the earlier email to any syntax which did not include a $.
That being said then, I think I favor parent->foo the best.
It’s not really a matter of syntax, but a matte of principle. We shouldn’t burden our users with another syntax to achieve the same thing.
cu,
Lars
Hi!
One other possible alternative would be to treat parent "like a variable..."
$parent->foo
That would be a big BC problem and also require serious changes to
handle it (look how $this is implemented - it's not regular variable at
all). So $parent is probably a non-starter.
Stanislav Malyshev, Software Architect
SugarCRM: http://www.sugarcrm.com/
(408)454-6900 ext. 227
Agreed. Some people may actually be using $parent as a variable name,
not difficult to imagine.
So far parent->foo seems to be the answer.
-Clint
Hi!
One other possible alternative would be to treat parent "like a variable..."
$parent->foo
That would be a big BC problem and also require serious changes to
handle it (look how $this is implemented - it's not regular variable at
all). So $parent is probably a non-starter.
--
-Clint
Agreed. Some people may actually be using $parent as a variable name, not
difficult to imagine.So far parent->foo seems to be the answer.
-Clint
My thoughts on the parent situations, as I'm not yet satisfied with the
current solution.
-
I tried to understand how the engine currently compiles and executes
object property fetches. I found it to be incredibly complex and I
certainly don't have the abilities to port this for statics. As such the
"parent::$foo" syntax is dead unless someone else is going to do the
necessary engine changes. -
I think the "parent->foo" syntax is nice in concept, but I think that
it's an absolute no-go as it doesn't fit in with the rest of PHP (and would
still require some engine changes those complexity I can't really
estimate). The parent->foo syntax is some off mix between parent::$foo
(which makes sense) and $parent->foo (which also makes sense). parent->foo
combines it in an odd way that doesn't look like PHP and adds yet another
new syntax for something that's going to be a rare case anyway. -
My suggestion is to avoid the engine and syntax related issues of parent
property access by putting this as a function in the standard library
instead. What I'm thinking about is a function like get_parent_property()
which returns the ReflectionProperty for it. Then you could do something
like this:public $foo {
get { return 'parent is: ' .
get_parent_property('foo')->getValue(); }
set($val) { get_parent_property('foo')->setValue($val . '
something'); }
}
I know that this is not an optimal solution, but I would much prefer this
over some new syntax like "parent->foo". Once (if) static properties have
better engine support we can switch to the cleaner parent::$foo way. But
until then I think that this is a good compromise, especially considering
that accessing the parent property shouldn't be a very common operation, so
it's okay if it's a bit more verbose.
This is just a rough idea of what I'd do. The exact way this would work
still needs further discussion. E.g. one could make passing the property
name optional and assume the current property as default. Or one could not
return a ReflectionProperty and instead provide two functions
get_parent_property and set_parent_property (what about isset and unset in
that case though?)
So, what do you think about this?
Nikita
Hi!
- I tried to understand how the engine currently compiles and executes
object property fetches. I found it to be incredibly complex and I
certainly don't have the abilities to port this for statics. As such the
"parent::$foo" syntax is dead unless someone else is going to do the
necessary engine changes.
Object property fetches are not that hard, but parent::$foo is static
fetch - which is entirely different beast which uses different opcodes
and works differently. And is not supported by object handlers - mainly
because there's no object :) If we designed it from scratch, we could
probably make a class an object of type Class and probably avoided that
problem, since we could just use standard object handlers for that, but
it is not what currently happens. If somebody feels ambitious he may
explore that direction.
- I think the "parent->foo" syntax is nice in concept, but I think that
it's an absolute no-go as it doesn't fit in with the rest of PHP (and
Well, it partially does, since ->foo part works exactly like in any
other expression. But parent-> part and the whole dollar-less variable
seems foreign for me too, so I am not happy with this one either.
would still require some engine changes those complexity I can't really
estimate). The parent->foo syntax is some off mix between parent::$foo
(which makes sense) and $parent->foo (which also makes sense).
$parent->foo makes tons of sense, but it means there's an object called
$parent. Now the question is where this one came from?
- My suggestion is to avoid the engine and syntax related issues of
parent property access by putting this as a function in the standard
library instead. What I'm thinking about is a function like
get_parent_property() which returns the ReflectionProperty for it. Then
you could do something like this:
This is an interesting approach. I like the idea of using the reflection
instead of inventing an awkward new syntax. However, I'm not sure I can
see how get_parent_property('foo') works - what exactly is 'foo' here?
Does it mean this function relies on implied $this? I'm not sure we
should be adding functions that do this. I'd rather have something like:
ReflectionProperty::getParentProperty($this, 'foo')
And yes, I know it's long-winded. But on the plus since it's generic,
does not pollute global space and might also be useful in other contexts.
Even more generic, we just could use existing ReflectionProperty like
this (this is standard API, no changes needed):
(new ReflectionProperty(get_parent_class(), 'foo'))->setValue($this, $val);
Yes, this is even more long-winded, that's why maybe we should have
shortcut function for it. Depends on how frequently in practice we
expect to do it.
I know that this is not an optimal solution, but I would much prefer
this over some new syntax like "parent->foo". Once (if) static
I like this approach more too.
properties have better engine support we can switch to the cleaner
parent::$foo way. But until then I think that this is a good compromise,
I'm afraid we couldn't though since parent::$foo already means something
else - it's a static property "$foo" of the class that is parent of
current class. We could redefine it in this specific context, in theory,
but that would be strange special case and I don't think it would be
good idea to do that. Our syntax kind of assumes the object has only one
class and all properties belong to this class, so we don't have a proper
syntax to express the idea of "same property, but with different scope".
This is just a rough idea of what I'd do. The exact way this would work
still needs further discussion. E.g. one could make passing the property
name optional and assume the current property as default. Or one could
If you assume current property you'd have to store it somewhere to pass
it and have API for that function to extract it, which sounds like very
tight coupling for this function. Maybe something like PROPERTY
would be better?
Stanislav Malyshev, Software Architect
SugarCRM: http://www.sugarcrm.com/
(408)454-6900 ext. 227
On Fri, Jan 11, 2013 at 1:03 AM, Stas Malyshev smalyshev@sugarcrm.comwrote:
- My suggestion is to avoid the engine and syntax related issues of
parent property access by putting this as a function in the standard
library instead. What I'm thinking about is a function like
get_parent_property() which returns the ReflectionProperty for it. Then
you could do something like this:This is an interesting approach. I like the idea of using the reflection
instead of inventing an awkward new syntax. However, I'm not sure I can
see how get_parent_property('foo') works - what exactly is 'foo' here?
Does it mean this function relies on implied $this?
I wrote that without actually checking the Reflection API, I forget that
one has to pass the object too. So my code samples should rather look like
this:
get_parent_property('foo')->getValue($this);
get_parent_property('foo')->setValue($this, ...);
And yes, I know it's long-winded. But on the plus since it's generic,
does not pollute global space and might also be useful in other contexts.
Re the ReflectionProperty::getParentProperty($this, 'foo') suggestion, is
this supposed to already get the value of the property (and there would be
an additional method ReflectionProperty::setParentProperty)?
Even more generic, we just could use existing ReflectionProperty like
this (this is standard API, no changes needed):
(new ReflectionProperty(get_parent_class(), 'foo'))->setValue($this, $val);
Yes, this is even more long-winded, that's why maybe we should have
shortcut function for it. Depends on how frequently in practice we
expect to do it.
I know that this is not an optimal solution, but I would much prefer
this over some new syntax like "parent->foo". Once (if) staticI like this approach more too.
properties have better engine support we can switch to the cleaner
parent::$foo way. But until then I think that this is a good compromise,I'm afraid we couldn't though since parent::$foo already means something
else - it's a static property "$foo" of the class that is parent of
current class. We could redefine it in this specific context, in theory,
but that would be strange special case and I don't think it would be
good idea to do that. Our syntax kind of assumes the object has only one
class and all properties belong to this class, so we don't have a proper
syntax to express the idea of "same property, but with different scope".
I try to see :: as a scope resolution operator rather than a static access
operator. For methods that's how it works (you can call instance methods
with it in a different scope, e.g. parent scope). So doing the same for
properties isn't far off. But yes, I do agree that this would be rather
tricky and could open another big can of worms (like we have with method
calls from incompatible contexts), so it might not actually make sense to
go down that path.
This is just a rough idea of what I'd do. The exact way this would work
still needs further discussion. E.g. one could make passing the property
name optional and assume the current property as default. Or one couldIf you assume current property you'd have to store it somewhere to pass
it and have API for that function to extract it, which sounds like very
tight coupling for this function. Maybe something like PROPERTY
would be better?
The current property can be obtained through
EG(current_execute_data)->function_state.function. This holds the accessor
function and the property can be taken from its name. Though this is
obviously all a bit dirty and is probably not a good idea. Probably better
to let people explicitly pass the property name.
Nikita
Even more generic, we just could use existing ReflectionProperty like
this (this is standard API, no changes needed): (new ReflectionProperty(get_parent_class(), 'foo'))->setValue($this, $val); Yes, this is even more long-winded, that's why maybe we should have shortcut function for it. Depends on how frequently in practice we expect to do it.
I like this idea as a solution to the problem. It would be ideal if
parent::$foo would work but since that is not currently a reasonable
option, either leaving the user land programmer to use reflection to do
it or to provide them with a shortcut way to do it is a good interim
solution to the problem.
I'm not sure that we really even need a 'shortcut' to do it, we'd need
some other people to chime in on that and how often the parent accessor
would want to be called.
> I know that this is not an optimal solution, but I would much prefer > this over some new syntax like "parent->foo". Once (if) static I like this approach more too.
+1 as well
> properties have better engine support we can switch to the cleaner > parent::$foo way. But until then I think that this is a good compromise, I'm afraid we couldn't though since parent::$foo already means something else - it's a static property "$foo" of the class that is parent of current class. We could redefine it in this specific context, in theory, but that would be strange special case and I don't think it would be good idea to do that. Our syntax kind of assumes the object has only one class and all properties belong to this class, so we don't have a proper syntax to express the idea of "same property, but with different scope".
I try to see :: as a scope resolution operator rather than a static
access operator. For methods that's how it works (you can call
instance methods with it in a different scope, e.g. parent scope). So
doing the same for properties isn't far off. But yes, I do agree that
this would be rather tricky and could open another big can of worms
(like we have with method calls from incompatible contexts), so it
might not actually make sense to go down that path.
I agree with that general sentiment as :: as a scope resolution
operator, it's just that right now, for ::$ that always translates to
static property access which is the current conundrum.
> This is just a rough idea of what I'd do. The exact way this would work > still needs further discussion. E.g. one could make passing the property > name optional and assume the current property as default. Or one could If you assume current property you'd have to store it somewhere to pass it and have API for that function to extract it, which sounds like very tight coupling for this function. Maybe something like __PROPERTY__ would be better?
The current property can be obtained through
EG(current_execute_data)->function_state.function. This holds the
accessor function and the property can be taken from its name. Though
this is obviously all a bit dirty and is probably not a good idea.
Probably better to let people explicitly pass the property name.Nikita
Is everyone okay with a long winded way to get/set the parent accessor
if necessary?
(new ReflectionProperty(get_parent_class(), 'foo'))->setValue($this, $val);
Alternatively, reflection in some cases takes an object instance
(ReflectionObject), we could extend ReflectionPropertyAccessor so that
it could take an object, then something that is slightly shortened would
work, like this:
(new ReflectionPropertyAccessor($this, 'foo'))->setValue(45);
That presently doesn't work, but could be made to work, especially
considering it's a new sub-class anyways.
If we don't like setValue() being different b/w ReflectionProperty and
ReflectionPropertyAccessor we could upgrade both classes to accept an
object instance as its constructor argument...
--
-Clint
Hi!
Re the ReflectionProperty::getParentProperty($this, 'foo') suggestion,
is this supposed to already get the value of the property (and there
would be an additional method ReflectionProperty::setParentProperty)?
I meant getting the ReflectionProperty class, but getting the actual
value is an option too. Of course, then it should be method on
ReflectionPropertyAccessor, since regular properties don't have this
thing. I'm not sure which is better - it depends on how much this would
be used. We could even not do anything special at all - as I said,
current reflection already has API to allow doing exactly this (well,
after property support is added), even if a bit long-winded.
The current property can be obtained through
EG(current_execute_data)->function_state.function. This holds the
accessor function and the property can be taken from its name. Though
this is obviously all a bit dirty and is probably not a good idea.
Probably better to let people explicitly pass the property name.
I agree. That's why I also mentioned having PROPERTY - this makes
copypasting methods a bit easier since you have less chances of making
typo in property names :)
Stanislav Malyshev, Software Architect
SugarCRM: http://www.sugarcrm.com/
(408)454-6900 ext. 227
Hi!
Re the ReflectionProperty::getParentProperty($this, 'foo') suggestion,
is this supposed to already get the value of the property (and there
would be an additional method ReflectionProperty::setParentProperty)?I meant getting the ReflectionProperty class, but getting the actual
value is an option too. Of course, then it should be method on
ReflectionPropertyAccessor, since regular properties don't have this
thing. I'm not sure which is better - it depends on how much this would
be used. We could even not do anything special at all - as I said,
current reflection already has API to allow doing exactly this (well,
after property support is added), even if a bit long-winded.The current property can be obtained through
EG(current_execute_data)->function_state.function. This holds the
accessor function and the property can be taken from its name. Though
this is obviously all a bit dirty and is probably not a good idea.
Probably better to let people explicitly pass the property name.I agree. That's why I also mentioned having PROPERTY - this makes
copypasting methods a bit easier since you have less chances of making
typo in property names :)Stanislav Malyshev, Software Architect
SugarCRM: http://www.sugarcrm.com/
(408)454-6900 ext. 227--
I'm guessing this RFC won't make it in PHP 5.5? Too bad since it did
seem like a very nice feature to have.
I don't know if it is very helpful but posting nevertheless. I'm a
C++/Qt/QML/PHP developer and for me the proposed syntax in the v1.2
document makes perfect sense and is intuitive to use. As for the
earlier example about isset...
i'd expect an example like this:
class SomeClass {
public $date {
get;
set(DateTime $date);
}
}
$temp = new SomeClass();
i'd expect "isset($temp->date)" to return exactly the same as if the
class where defined like this:
class SomeClass {
public $date;
}
I don't know if that issue was already sorted out but i wanted to
share my expectations in that regards.
Will this make PHP 5.5? Or will it be deferred to 5.6?
I'm guessing this RFC won't make it in PHP 5.5? Too bad since it did
seem like a very nice feature to have.I don't know if it is very helpful but posting nevertheless. I'm a
C++/Qt/QML/PHP developer and for me the proposed syntax in the v1.2
document makes perfect sense and is intuitive to use. As for the
earlier example about isset...i'd expect an example like this:
class SomeClass {
public $date {
get;
set(DateTime $date);
}
}$temp = new SomeClass();
i'd expect "isset($temp->date)" to return exactly the same as if the
class where defined like this:class SomeClass {
public $date;
}
That's correct, it will.
Furthermore, isset() will never throw an error, even if it would not be
legal to call isset or get (from 1.1 RFC changes).
I don't know if that issue was already sorted out but i wanted to
share my expectations in that regards.Will this make PHP 5.5? Or will it be deferred to 5.6?
Nikita, Stas and I are working very hard to make it into 5.5, we expect
to propose a vote sometime in the next couple of days.
** Any remaining feedback is appreciated, the RFC is being kept up to
date w/ current understood consensus. **
--
-Clint
Why we must have parent property access at all? What's the use case and how do other langs do it?
Am I right to say there is no "parent property", this would just call the parent's [gs]etter using the same underlying property value?
Steve
Agreed. Some people may actually be using $parent as a variable name, not
difficult to imagine.So far parent->foo seems to be the answer.
-Clint
My thoughts on the parent situations, as I'm not yet satisfied with the
current solution.
I tried to understand how the engine currently compiles and executes
object property fetches. I found it to be incredibly complex and I
certainly don't have the abilities to port this for statics. As such the
"parent::$foo" syntax is dead unless someone else is going to do the
necessary engine changes.I think the "parent->foo" syntax is nice in concept, but I think that
it's an absolute no-go as it doesn't fit in with the rest of PHP (and would
still require some engine changes those complexity I can't really
estimate). The parent->foo syntax is some off mix between parent::$foo
(which makes sense) and $parent->foo (which also makes sense). parent->foo
combines it in an odd way that doesn't look like PHP and adds yet another
new syntax for something that's going to be a rare case anyway.My suggestion is to avoid the engine and syntax related issues of parent
property access by putting this as a function in the standard library
instead. What I'm thinking about is a function like get_parent_property()
which returns the ReflectionProperty for it. Then you could do something
like this:public $foo {
get { return 'parent is: ' .
get_parent_property('foo')->getValue(); }
set($val) { get_parent_property('foo')->setValue($val . '
something'); }
}I know that this is not an optimal solution, but I would much prefer this
over some new syntax like "parent->foo". Once (if) static properties have
better engine support we can switch to the cleaner parent::$foo way. But
until then I think that this is a good compromise, especially considering
that accessing the parent property shouldn't be a very common operation, so
it's okay if it's a bit more verbose.This is just a rough idea of what I'd do. The exact way this would work
still needs further discussion. E.g. one could make passing the property
name optional and assume the current property as default. Or one could not
return a ReflectionProperty and instead provide two functions
get_parent_property and set_parent_property (what about isset and unset in
that case though?)So, what do you think about this?
Nikita
Hi Clint,
Am 04.01.2013 um 04:13 schrieb Clint Priest cpriest@zerocue.com:
[...]
- It forces the user to access the parent property accessor in a different way than is used everywhere else
Isn’t that the same as parent->$foo? Please let’s not introduce a special syntax for something that can be done properly. I would either go with variant 2 ("Rewrite the way static property references work"). If that takes too long and we feel that a version without parent access is sufficient, we should disallow parent access for version 1 of property accessors.
cu,
Lars
Please note that I have updated and clarified v1.2 with some recent
feedback:
-
Steve Clay suggested the term Guarded Property and dropping
Shadowing. The "Shadowing" section has been renamed to "Guarding," some
of the wording was updated and the "For Additional Clarity" was updated
with nearly identical wording Steve suggested which I think brings even
greater clarity. -
The parent::$foo issue I recently posted about was changed near the
end of the "Overloading Properties" section, which shows the proposed
parent->$Milliseconds form. Seeing it color coded in this light makes
it even more appealing and apparent I feel. -
Removal of Accessor section was added. This was brought up as a
question as to what happens and I felt what should be done is what is
defined there.
Other possible alternatives to number 3:
3a) The public $Foo = 5 declaration would not "shed" it's inherited
accessors, they would remain or could be re-defined (but not removed).
3b) The public $Foo = 5 declaration would not be allowed with the = 5
and it would not shed its inherited accessors as 3a
For maximum flexibility, I favor the proposed solution as it provides
for the most flexibility, it would allow sub-classes to re-define a
guarded property as a traditional property, or keep it as a guarded
property (modifying the getter, adding a setter, etc).
Here is the updated RFC incorporating the feedback from previous
rounds of discussion.https://wiki.php.net/rfc/propertygetsetsyntax-v1.2
I'm posting it for final review so I can move to voting on Jan 7th.
Please note that the current fork is not quite up-to-date with the RFC
but will be within a few more days.-Clint
--
-Clint
Here is the updated RFC incorporating the feedback from previous rounds
of discussion.
One thing that I have not found in the RFC is how do you specify a
default value AND accessors?
Many examples show things like:
protected $Seconds = 3600;
public $Hours {
get { return $this->Seconds / 3600; }
set { $this->Seconds = $value * 3600; }
}
But what if I want to store something in a different internal
representation for example with a default. Is this valid syntax?
public $hexFoo = 10 {
get { return dechex($this->foo); }
set { $this->hexFoo = hexdec($value); }
}
Or is it this?
public $hexFoo {
get { return dechex($this->foo); }
set { $this->hexFoo = hexdec($value); }
} = 10;
Is it possible at all?
Cheers
--
Jordi Boggiano
@seldaek - http://nelm.io/jordi