Variables that are attached to a class, commonly known as “class members”, are sometimes referred to as “properties” in PHP. In order to avoid confusion, from here on forward, variables attached to a class shall be called “class members”, while they syntax proposed in this article will be the only thing called a “property”.
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.
Currently, to emulate properties and practice class encapsulation, properties must be implemented in userland, as a pair of “get” and “set” methods. Get and set methods are a common programming practice when using classes and encapsulation. In userland, one might create a method in a class called GetMyVar() which will return the contents of a private variable called $myVar. This way, someone using the class can only read or “get” the variable, but not write or “set” it. This gives the class designer the guarantee that the contents of $myVar wont be tampered with. Similarly, one might create a method in that class called SetMyVar() which takes one parameter, and stores it in $myVar. Additionally, one might add some kind of validation in this method, to ensure that only the right kind of data will be stored in $myVar. It is easy to see how get and set methods quickly become useful. However, instead of accessing a property $object->myVar; you now have to use a set of methods instead: $object->GetMyVar(); $object->SetMyVar('foo');.
PHP's current solution to get and set accessors are the magic methods __get() and __set(). These methods allow properties to be created, but all of the “get” calls for every property in a class are routed through a single method, __get(), as are the “set” calls, through __set(). Additionally, it is not immediately clear to the programmer what properties are available in a class when skimming the code, as the property “definitions” are hidden in the implementation of __get() and __set(). This can cause additional problems with IDE features like intellisense.
This works great for this type of situation:
class TimeSheet { // An array full of times in seconds private $time_list; public function __get($name) { // Return the time in hours, not seconds return $this->time_list[$name] / 3600; } public function __set($name, $value) { // Store the time in seconds, not hours $this->time_list[$name] = $value * 3600; } };
$sheet = new TimeSheet(); $sheet->foo = 12;// Stored as 43200 echo $sheet->foo;// Outputs 12
In the above example, the TimeSheet class can store a set of time periods. When accessing the properties of the class, you give and receive hours, yet the time periods are stored as seconds in the $time_list array. The magic methods __get() and __set() work great here because the same code is applied to every property.
What if we want totally different code to apply to each property?
class TimePeriod { // A time period, stored as seconds private $seconds; public function __get($name) { switch ($name) { case 'seconds': return $this->seconds; break; case 'minutes': return $this->seconds / 60; break; case 'hours': return $this->seconds / 3600; break; } return null; } public function __set($name, $value) { switch ($name) { case 'seconds': $this->seconds = $value; break; case 'minutes': $this->seconds = $value * 60; break; case 'hours': $this->seconds = $value * 3600; break; } } };
$time = new TimePeriod(); $time->seconds = 12;// Stored as 12 $time->minutes = 12;// Stored as 720 $time->hours = 12;// Stored as 43200 echo $time->seconds;// Outputs 43200 echo $time->minutes;// Outputs 720 echo $time->hours;// Outputs 12
In the above example, the code in the __get() and __set() methods quickly becomes very large, complicated, and difficult to read. Imagine how cumbersome the code would get adding the properties days, weeks, months, and years. What if we also wanted to add other properties?
Broken Inheritance
Now what happens if a second class extends this class. If the extending class were to define an empty __get() and __set() method, the properties would cease to exist in the child class. This goes against the principles of inheritance, as something that once existed in the parent, no longer does in the child. By using a proper property syntax, once a property is defined it would be guaranteed to exist in all child classes, with the same or greater visibility.
The current implementations of getters and setters, or properties, in various programming languages almost all have a different syntax. In order to avoid confusing users, an existing syntax should be followed as closely as possible. PHP's syntax is often similar to C#'s making it a good reference point. Additionally, C# has a clean, clear and easily scanned syntax for properties.
The syntax for properties in C# is as follows:
class TimePeriod { private double seconds; public double Hours { get { return seconds / 3600; } set { seconds = value * 3600; }// The variable "value" holds the incoming value to be "set" } }
TimePeriod time = new TimePeriod(); // Assigning the Hours property causes the 'set' accessor to be called. time.Hours = 24; // Evaluating the Hours property causes the 'get' accessor to be called. System.Console.WriteLine("Time in hours: " + time.Hours);
This is the recommendation for a property syntax in PHP.
class TimePeriod { private $seconds; // Properties are implemented using the "property" keyword, just like functions/methods use the "function" keyword public property Hours { get { return $this->seconds / 3600; } set { $this->seconds = $value * 3600; }// The variable $value holds the incoming value to be "set" } };
// 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” as seen above would become new keywords. Alternatively, “get” and “set” could be made to only have meaning if they are in a prototype, similar to the current type hinting syntax that exists in php-trunk.
Alternative Syntax
The syntax above attempts to match the syntax of C# properties, as well as PHP method declarations as much as possible. An alternative syntax could look like the following:
class TimePeriod { private $seconds; // Looks less like a function and more like a class member // This creates yet another property syntax by moving away from the C# syntax too much public $Hours { get { return $this->seconds / 3600; } set { $this->seconds = $value * 3600; }// The variable $value holds the incoming value to be "set" };// Note the semi-colon here };
In this syntax, a semi-colon exists at the end of the property definition. This was suggested by Kalle Nielsen, as it would be simpler to implement into the current implementation of class members in the PHP interpreter.
Alternative Syntax Suggested By jbondc
property Hours { get { return $this->seconds / 3600; } set { $this->seconds = $value * 3600; } // The variable $value holds the incoming value to be "set" } class TimePeriod { private $seconds; public [Hours] $hours; }
This syntax would favor re-use similar to traits by injecting the set/get code.
The implementation would add the keywords 'property', _PROPERTY_, the '[', ']' tokens and the 'readonly' property as part of Spl.
A read-only property could be defined in the following ways:
// Spl property property readonly { final set { throw Exception(__PROPERTY__ . " is read-only."); } } // Read-only property #1 property MyHours1 extends Hours { use readonly; } // Read-only property #2 property MyHours2 extends Hours { set { throw Exception(__PROPERTY__ . " is read-only."); } } // Read-only property #3 (if you don't want an exception) property MyHours3 extends Hours { set { trigger_error(__PROPERTY__ . " is read-only.", E_USER_ERROR); } }
Another approach, to reserve the '[', ']' tokens for annotations or something else could be:
// property as some kind of trait class TimePeriod { private $seconds; public {use Hours;} $hours; }
A property can be read-write, read-only or write-only. This example shows the latter two:
class TimePeriod { private $seconds; // This property has no "set" method, and therefore is read-only public property Hours { get { return $this->seconds / 3600; } } // This property has no "get" method, and therefore is write-only public property Minutes { set { $this->seconds = $value * 60; } } };
$time = new TimePeriod(); $time->Minutes = 720;// Stored as 43200 $time->Hours = 12;// Error, this property is read-only echo $time->Hours;// Outputs 12 echo $time->Minutes;// Error, this property is write-only
What about the readonly keyword?
There has been talk about adding a “readonly” keyword to class members in PHP, so why not use it to define read-only properties? The short answer is, the readonly keyword does not provide the same functionality. When a property without a set method (a “read-only” property) is overloaded in a child class, the set method can then be implemented and used. However, if that property was set defined using the readonly keyword, the child class would not be allowed to implement a set method.
The following is an example of using the “readonly” keyword with a property:
class TimePeriod { private $seconds; public readonly property Hours { get { return $this->seconds / 3600; } } }; class HalfTimePeriod extends TimePeriod { public property Hours { // The get method is being overloaded set { return ($this->seconds / 3600) / 2; }// Error, properties with the readonly keyword cannot have a set method } };
Additionally, there is no talk of a “writeonly” keyword for PHP, so write-only properties would not be possible.
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; public property Hours { get { return $this->seconds / 3600; } protected set { $this->seconds = $value * 3600; } } };
$time = new TimePeriod(); $time->Hours = 12;// Stored as 43200 echo $time->Hours;// Error, this property is read-only
Interfaces may define property declarations, without a body. The purpose of this is to define properties that must exist in an implementing class, and indicate if they are read-write, read-only, or write-only.
When a class implements an interface that defines a read-only property, 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 write-only property. 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 property MyProperty { get; set; } public property MyReadOnlyProperty { get; } public property MyWriteOnlyProperty { set; } };
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; public property Hours { get { return $this->seconds / 3600; } set { $this->seconds = $value * 3600; } } // This property is read-only public property Minutes { get { return $this->seconds / 60; } } public property Milliseconds { // This method is public get { return $this->seconds * 60; } // This method is protected protected set { $this->seconds = $value * 3600; } } }; class HalfTimePeriod extends TimePeriod { public property Hours { // The get method is being overloaded get { return ($this->seconds / 3600) / 2; } // Notice that we are not also overloading the set method // The base set method will still be called } public property Minutes { // A set method is added, turning this property into a read-write property instead of read-only set { $this->seconds = $value * 60; } } public property 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:: could also be used here) TimePeriod::$Milliseconds = $value; } } };
Properties declared final are not allowed to be overloaded in a child class, just like final methods.
class TimePeriod { private $seconds; public final property 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 property 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 property 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 property Hours { get { return ($this->seconds / 3600) / 2; }// This attempt to overload the get method of the "Hours" will throw an error // because it was declared final in the base class set ( $this->seconds = ($value * 3600) * 2; )// This would be OK } };
Static properties act nearly identical to regular properties, except in a static context.
class TimePeriod { private static $seconds; public static property 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
An implementation of this proposal is being worked on by Clint Priest <phpdev at zerocue dot com>. Information about implementation details can be found here: https://wiki.php.net/rfc/propertygetsetsyntax-as-implemented