This document describes the accessor syntax As Implemented. The RFC which the implementation was crafted from is located here: https://wiki.php.net/rfc/propertygetsetsyntax
Properties provide a clean, easy to understand and unified syntax for get/set accessors. They allow incoming and outgoing requests (gets and sets) from a class member to be run through a method first. This method can perform validation or a transformation, or update other areas of the class. Properties do not even have to be associated with a class member, and can generate their own data on-the-fly.
This is the property syntax for accessors as implemented.
class TimePeriod { private $Seconds; // Accessor properties are implemented just like you would define an actual property public $Hours { get { return $this->Seconds / 3600; } // In the set accessor, the variable $value holds the incoming value to be "set" set { $this->Seconds = $value * 3600; } } }
// Accessing the property is the same as accessing a class member $time = new TimePeriod(); $time->Hours = 12; // Stored as 43200 echo $time->Hours; // Outputs 12
Note that “get” and “set” were not made to be new keywords, they are processed as strings by the parser.
Properties can be overloaded in extending classes. An overloaded property can replace an existing get or set declaration without touching the other, replace both the get and set declarations, or add an omitted get or set declaration turning the property into a read-write property. Additionally, a property may have its visibility increased through overloading. Get or set declarations cannot be removed or hidden by the child class in any way.
class TimePeriod { protected $Seconds = 3600; public $Hours { get { return $this->Seconds / 3600; } set { $this->Seconds = $value * 3600; } } // This property is read-only public $Minutes { get { return $this->Seconds / 60; } } /* public getter, protected setter */ public $Milliseconds { get { return $this->Seconds * 1000; } protected set { $this->Seconds = $value / 1000; } } } class HalfTimePeriod extends TimePeriod { /* Overload getter, inherit setter */ public $Hours { get { return ($this->Seconds / 3600) / 2; } /* The base setter method will be inherited */ } public $Minutes { // A set method is added, turning this property into a read-write property instead of read-only set { $this->Seconds = $value * 60; } } public $Milliseconds { // A property method can have its visibility increased in a child class, just like regular PHP methods // This method is now public instead of protected public set { // You can access a base class property explicitly, just like accessing a base class member or method parent::$Milliseconds = $value; } } }
Note that if parent:: scope access would be overloaded, resolution priorities follow:
This does not conflict with existing functionality which is presently only #3.
Properties can have different levels of visibility for the get and set methods. This is achieved by setting either the get or set method to a lower visibility than the property is set to.
class TimePeriod { private $Seconds = 3600; public $Hours { get { return $this->Seconds / 3600; } protected set { $this->Seconds = $value * 3600; } } }
$o = new TimePeriod(); echo $o->Hours; // Prints 1 $o->Hours = 12; // Error, unable to set protected property
In the above example the getter inherits the public access level of the property definition.
To facilitate complete functionality with accessors it is necessary to provide accessor functions to act on isset() and unset() calls. These operate just like their magic __isset() and __unset() functions but are definable within the accessor block.
class TimePeriod { private $Seconds = 3600; public $Hours { get { return $this->Seconds / 3600; } set { $this->Seconds = $value; } isset { return isset($this->Seconds); } unset { unset($this->Seconds); } } }
You may also use automatic implementations of property accessors by not defining a body to the accessor. Doing so causes an automatic implementation to occur. Automatic implementations create a protected backing field automatically, named the same as the property with a double underscore (__) preceding it. The get and set implementations directly get and set from this automatic backing field.
The isset automatic implementation tests for the property to be non-null. (See php equivalent below) The unset automatic implementation sets the property to be null. (See php equivalent below)
class TimePeriod { // Accessor properties are implemented just like you would define an actual property public $Hours { get; set; isset; unset; } }
Translates to this:
class TimePeriod { // Accessor properties are implemented just like you would define an actual property protected $__Hours; public $Hours { get { return $this->__Hours; } set { $this->__Hours = $value; } isset { return $this->Hours != NULL; } unset { $this->Hours = NULL; } } }
Note that isset/unset implementations will always be provided (where appropriate) if they are not defined or if they are explicitly auto-defined (as above).
“Where appropriate” means that if you have only defined a getter then unset is not available, likewise if you have only defined a setter, then isset is not available.
Lastly, isset/unset may be explicitly defined as indicated in prior sections, the “will always be provided” only applies to cases where they are not explicitly defined.
Properties declared final are not allowed to be overloaded in a child class, just like final methods.
class TimePeriod { private $Seconds; public final $Hours { get { return $this->Seconds / 3600; } set { $this->Seconds = $value * 3600; } } } class HalfTimePeriod extends TimePeriod { private $Seconds; // This attempt to overload the property "Hours" will throw an error because it was declared final in the base class public $Hours { get { return ($this->Seconds / 3600) / 2; } } }
Final property methods
The get or set method of a property can be declared “final” independently of each other. This would allow for one of them to be overloaded, but not the other.
class TimePeriod { private $Seconds; // Notice there is no "final" keyword on the property declaration public $Hours { final get { return $this->Seconds / 3600; }// Only the get method is declared final set { $this->Seconds = $value * 3600; } } } class HalfTimePeriod extends TimePeriod { private $Seconds; public $Hours { // This attempt to overload the get method of the "Hours" will throw an error // because it was declared final in the base class get { return ($this->Seconds / 3600) / 2; } // This would be accepted set ( $this->Seconds = ($value * 3600) * 2; ) } }
Static properties act identically to regular properties, except in a static context.
class TimePeriod { private static $Seconds; public static $Hours { get { return self::$Seconds / 3600; } set { self::$Seconds = $value * 3600; } } }
Accessing a static property is the same as accessing a static class member
TimePeriod::$Hours = 12; // Stored as 43200 echo TimePeriod::$Hours; // Outputs 12
Parent accessors may also be used
class TimePeriod2 extends TimePeriod { public static $Hours { get { return parent::$Seconds / 3600; } set { parent::$Seconds = $value * 3600; } } }
Functions such as sort() require a reference to the underlying data storage value in order to modify them, in these cases you can place the & before the get to indicate the returning of a reference variable.
class SampleClass { private $_dataArray = array(1,2,5,3); public $dataArray { &get { return $this->_dataArray; } } } $o = new SampleClass(); sort($o->dataArray); /* $o->dataArray == array(1,2,3,5); */
The following operators have tests written for them and work as though it were any other variable. If the operator attempts to make a change to a property for which no setter is defined, it will produce an error such as “Cannot set property xxx, no setter defined.” If a setter is defined, then the assignment operator works as expected.
The following operators have code tests written already: Pre/Post Increment/Decrement, Negation, String Concatenation (.), +=, -=, *=, /=, &=, |=, +, -, *, /, %, &, |, &&, ||, xor, ~, ==, ===, !=, !==, >, <, >=, <=, .=, <<, >>, Array Union (array + array), instanceof
Defining accessors with only a getter or only a setter will make them read only and write only respectively but this does not enforce anything with subclasses.
You could declare a get-only accessor final in order to enforce rules upon subclasses but this would also prevent any changes to that getter by a subclass, which may not be the desired outcome.
What may be needed instead is to allow a subclass to alter the getter but still enforce that the property must remain read-only. That is the use case the following new keywords, read-only and write-only, address.
new keyword
You can use the read-only keyword in the definition of the property to enforce the property as read only. No setter may be defined and an attempt to set the property results in a fatal error.
class TimePeriod { private $Seconds; // This property has specified the read-only keyword and therefore is read-only public read-only $Hours { get { return $this->Seconds / 3600; } // Setter may not be defined } } // Results in Fatal Error $o = new TimePeriod(); $o->Hours = 4;
new keyword
You can use the write-only keyword in the definition of the property to enforce the property as write only. No getter may be defined and an attempt to get the property results in a fatal error.
class TimePeriod { private $Seconds; // This property has specified the write-only keyword and therefore is write only public write-only $Hours { // Getter may not be defined set { $this->Seconds = $value * 3600; } } } // Results in Fatal Error $o = new TimePeriod(); echo $o->Hours;
The read-only and write-only keywords are inherited and must be declared by child classes.
class TimePeriod { private $Seconds; // This property has specified the read-only keyword and therefore is read-only public read-only $Hours { get { return $this->Seconds / 3600; } // Setter may not be defined } } class TimePeriod2 extends TimePeriod { /* Results in fatal error, $Hours must be declared as read-only, * as in parent which would further disallow the set {} from be declared. */ public $Hours { get { return $this->Seconds / 3600; } set { $this->Seconds = $value; } } }
Interfaces may define property declarations, without a body. The purpose of this is to define properties that must exist in an implementing class, and may indicate if they are read-write, read-only, or write-only.
When a class implements an interface that defines a getter, it can add in a set method to turn the property into a read-write property. The inverse is also true for implementing an interface with a setter only. This is because interfaces are designed to enforce what should be in a class, and not what should not be in a class.
interface iSampleInterface { public $MyProperty { get; set; isset; unset; } }
Accessors work as expected with traits including automatic accessor properties. You can use any feature with traits that you could with classes including asymmetrical access levels, read-only/write-only, isset, unset, etc.
trait SampleTrait { private $Seconds = 3600; public $Hours { get { return $this->Seconds * 3600; } set { $this->Seconds = $value / 3600; } } }
The new class has all of the same functions as the ReflectionProperty as well as:
All other functions have been updated to return appropriate values and/or provide appropriate actions for an accessor.
A fairly extensive test-suite has been created to test the functionality as well.
There are no known backward compatibility issues.
Accessor information is stored in a new zend_accessor_info struct. These structures are stored in a new HashTable property in a zend_class_entry structure named accessors. They are indexed by the hash_value of the property name and are thus quickly accessed during property resolution.
typedef struct _zend_accessor_info { zend_uint flags; const char *doc_comment; int doc_comment_len; zend_function *getter; zend_function *setter; zend_function *isset; zend_function *unset; } zend_accessor_info;
Internally the accessors are implemented as ordinary functions (with appropriate access levels) with specialized names. get/set/isset/unset for a property named $Hours would be __getHours(), __setHours($value), __issetHours() and __unsetHours() respectively.
Two new function flags have been defined:
#define ZEND_ACC_READONLY 0x20000000 #define ZEND_ACC_WRITEONLY 0x40000000
An additional byte was added to zend_internal_function:
zend_uchar purpose;
This was in lieu of using 4 additional flag values for which there was not room. There are presently five states purpose can be in, they are:
#define ZEND_FNP_UNDEFINED 0 /* No special purpose function */ #define ZEND_FNP_PROP_GETTER 1 /* Special purpose accessor: getter */ #define ZEND_FNP_PROP_SETTER 2 /* Special purpose accessor: setter */ #define ZEND_FNP_PROP_ISSETTER 3 /* Special purpose accessor: issetter */ #define ZEND_FNP_PROP_UNSETTER 4 /* Special purpose accessor: unsetter */
* __get(), __set(), __isset() and __unset() guards were used and the functionality is the same with the new accessors.
* Error producing lines have been modified to check the function for ZEND_ACC_IS_ACCESSOR mask with more appropriate error report occurring. For example: Cannot override final property getter TimePeriod::$Hours
There was no built-in mechanism to handle custom get/set/isset/unset for static properties, these were handled by catching references to static properties, checking for the existence of a static accessor and converting the compilation into a function call. When a static setter is being used, the compiled code first becomes a static getter call and the zend_do_assign backpatches the op_array to become a call to the setter, as appropriate.
This yielded the possibility that a getter call was being made while it should not be allowed (if there was no getter defined) and so pass_two() was changed to look for these non-backpatched illegal static getter calls and a compile time error is produced.
read-only and write-only keywords
These keywords may not be specified multiple times, nor may they be used with regular properties (non-accessor) or methods.