Blog Archive

Monday, 23 September 2013

Public properties, getters and setters, or magic?

Opinion seems to be divided on whether it is good practice to use public properties in PHP classes, or whether to use getter and setter methods instead (and keep the properties private or protected). A sort of hybrid third option is to use the magic methods __get() and __set(). As always, there are advantages and disadvantages to each approach, so let's take a look at them...

Public properties example

class Foo 
{
    public $bar;
}

$foo = new Foo();
$foo->bar = 1;
$foo->bar++;

In this example, bar is a public property of Foo. The calling code can manipulate that property in any way it likes, and stuff any old data in it.

Public properties advantages

  • Compared with getter and setter methods, there is a lot less typing involved!
  • The calling code is more readable and easier to work with than getter and setter method calls.
  • A call to a public property (to either set or get) is faster and uses less memory than a method call - but the saving is small unless you are calling it many times in a long loop.
  • Objects with public properties can be used as parameters for some PHP functions (such as http_build_query), and can be serialized by json_encode.

Public properties disadvantages

  • There is no way of controlling what data is held in the properties - it is very easy to populate them with data that the class methods might not expect. This can be particularly problematic if the class is being used by another developer (ie. other than the author of the class), as they might not (and should not need to) be aware of the internal workings of the class (and let's face it, after a few months, the author of the class probably won't remember the internal workings either).
  • If you want to expose a public property in an API, you can't do so using an interface, as in PHP interfaces only allow method definitions.

Using getters and setters example

class Foo
{
    private $bar;
    
    public function getBar()
    {
        return $this->bar;
    }
    
    public function setBar($bar)
    {
        $this->bar = $bar;
    }
}

$foo = new Foo();
$foo->setBar(1);
$foo->setBar($foo->getBar() + 1);

Here the bar property is private so cannot be accessed by the calling code directly. The caller has to use the getBar method to retrieve the value and the setBar method to set it. The methods can perform validation processing to ensure that only valid values are allowed through.

Getters and setters advantages

  • With getters and setters you can control exactly what is stored in your object properties, and reject any values that are not valid.
  • You also have the option of performing additional processing when a value is set or retrieved (for example, if updating this property should trigger some action such as notifying an observer).
  • When setting a value which represents an object or array (rather than a scalar value), you can specify the type in the function declaration (eg. public function setBar(Bar $bar)). Such a damn shame PHP won't let you do the same thing with ints and strings!
  • If the value of a property has to be loaded from an external data source or the runtime environment, you can lazy load it - so the resources required to load the data are only used if the property itself is called. Of course you would need to be careful that you don't needlessly load the data from the external source on every call to the property. And it would be more common to make a single call to the database to populate all the properties rather than fetching them one at a time.
  • You can make properties read-only or write-only by only creating a getter but not a setter or vice-versa.
  • You can include a getter and setter in an interface to expose them in an API.

Getters and setters disadvantages

  • For developers who are used to accessing properties directly, getters and setters are a pain in the neck to use! For every property you have to define the property itself, a getter, and a setter, and to use the property in the calling code you have to make extra method calls - it is much easier to say $foo->bar++; rather than the long-winded $foo->setBar($foo->getBar() + 1); (although of course you could add yet another method such as $foo->incrementBar();)
  • As noted above, there is a small additional overhead when making a method call over using a plain ol' public property.
  • By convention, getter and setter methods start with the verbs 'get' and 'set' respectively, but these verbs are also commonly used for other methods which don't necessarily relate to properties. This is not necessarily a problem for the calling code as you might not care what the implementation is, but the ambiguity about the type of implementation you are dealing with can make the code harder to understand.

Magic method getters and setters example

class Foo
{
    protected $bar;

    public function __get($property)
    {
        switch ($property)
        {
            case 'bar':
                return $this->bar;
            //etc.
        }
    }

    public function __set($property, $value)
    {
        switch ($property)
        {
            case 'bar':
                $this->bar = $value;
                break;
            //etc.
        }
    }
}

$foo = new Foo();
$foo->bar = 1;
$foo->bar++;

Here, the bar property is not publicly exposed, but the calling code still calls it as though it were public. When PHP can't find a matching public property, it calls the appropriate magic method (__get() for retrieving a value, __set() for assigning a value). This might seem like the best of both worlds, but it has a major drawback (see disadvantages below!). Note also that __get and __set are NOT called if a matching public property exists, but they are called if a protected or private property exists but is out of scope, or if no property exists.

Magic method getter and setter advantages

  • You can manipulate properties directly from the calling code (as though they were public properties) but still have complete control over what data goes into what property.
  • As with declared getter and setter methods, you can perform additional processing when the property is used.
  • You can use lazy loading.
  • You can make properties read-only or write-only.
  • You can use the magic methods as a catch-all to handle calls to properties that don't exist, and still process them in some way (effectively allowing the calling code to decide what property names to use).

Magic method getter and setter disadvantages

  • The showstopper for magic methods is that they do not expose the property in any way. To use or extend the class, you have to 'just know' what properties it supports. This is simply unacceptable most of the time (unless perhaps you are one of those hard core programmers who think notepad is an IDE!), although there are times when the advantages mentioned above outweigh this limitation. As pointed out by a commentator below, this disadvantage can be overcome by using the phpDoc tags @property, @property-read, and @property-write. Cool.

Which option to use

Clearly there are some significant advantages to getters and setters, and some people feel that they should therefore be used all the time (especially those who come from a Java background!). But in my opinion, they do break the natural flow of the language, and their additional complexity and verbosity puts me off using them unless there is a need to do so (I find it a little irritating when naive getters and setters don't actually DO anything other than get or set the property). My choice then is to use public properties most of the time, but getters and setters for critical settings that I feel need stricter control, would benefit from lazy loading, or that I want to expose in an interface.

Another alternative?

Before I learned PHP, I used to use C#. In C#, all properties have accessor methods, but you don't have to call them as methods, you can just manipulate the properties directly and the corresponding methods are called magically. So it is similar to PHP's magic __get and __set, but the properties are still declared and exposed. This really is the best of both worlds, and it would be great to see a feature like this in PHP.

Tragically though, the RFC for a C#-like property accessor syntax feature could not quite muster up the two-thirds majority needed to take it forward: https://wiki.php.net/rfc/propertygetsetsyntax-v1.2 Bah!