7

What's considered best practice writing OOP classes when it comes to using a property internally.

Consider the following class;

<?php
Class Foo
{
    /**
     * @var null|string
     */
    protected $_foo;

    /**
     * @return null|string
     */
    public function getFoo()
    {
        return $this->_foo;
    }

    protected function _doSomething()
    {
        $foo    = $this->_foo;
        $result = null;
        // ...
        return $result;
    }
}

As you see im using the property _foo in _doSomething() although a sub-class could override getFoo() returning a computed value not stored back into _foo; that's a flaw.

What should i do?

  1. mark getter as final, use property internally (no extra function calls, enforce end-developer to use _foo as a property since it's protected)
  2. use getFoo() internally, mark _foo as private (extra function calls)

Both options are waterproof however im seriously concerned about all the extra function calls so i tend to use option 1 however option 2 would be more "true OOP" imho.

Also read http://fabien.potencier.org/article/47/pragmatism-over-theory-protected-vs-private which also suggests option 2 :/

Another related question; If a property has a setter should that property be private, enforcing end-developers to use it in sub-classes or should it be an unwritten rule the programmer is responsible to set a valid property value?

Roland Franssen
  • 1,038
  • 1
  • 11
  • 22
  • Option 3: Use regular `public` properties and believe, that other developers can read doc comments. However, thats just a design-question, so it's hard to tell something right and something else wrong. (Sidenote: In the OOP- _theory_ properties define the state and methods define the behaviour. In this context getters/setters are wrong at all, because accessing a property is not a behaviour) – KingCrunch Dec 31 '11 at 16:00
  • public properties are a bad idea, they allow external agents to futz with the internal state of the class. – GordonM Dec 31 '11 at 17:00
  • Also there is no such thing as "readonly properties" in PHP, so you kinda have to... (besides magic __get, which is implicit; therefor i favor a getter) – Roland Franssen Dec 31 '11 at 18:03

1 Answers1

5

The second approach is, as you say, the more correct way according to OOP. You're also right that there is more cost in terms of CPU cycles in calling a method than in accessing a property as a variable. However, this will in most cases fall into the category of a micro-optimization. It won't have a noticeable effect on performance except if the value in question is being used heavily (such as in the innermost part of a loop). Best practice tends to favour the correct over the most performant unless the performance is genuinely suffering as a result of it.

For simple variables the use of a getter internally is not immediately obvious, but the technique comes into its own if you're dealing with a property that gets populated from an external data source such as a database. Using the getter allows you to fetch the data from the DB in a lazy way, ie on demand and not before it's needed. For example:

class Foo
{
    // All non-relevent code omitted
    protected $data = NULL;

    public class getData ()
    {
        // Initialize the data property
        $this -> data = array ();
        // Populate the data property with a DB query
        $query = $this -> db -> prepare ('SELECT * FROM footable;');
        if ($query -> execute ())
        {
            $this -> data = $query -> fetchAll ();
        }
        return ($this -> data);
    }

    public function doSomethingWithData ()
    {
        $this -> getData ()
        foreach ($this -> data as $row)
        {
            // Do processing here
        }
    }
}

Now with this approach, every time you call doSomethingWithData the result is a call to getData, which in turn does a database query. This is wasteful. Now consider the following similar class:

class Bar
{
    // All non-relevent code omitted
    protected $data = NULL;

    public class getData ()
    {
        // Only run the enclosed if the data property isn't initialized
        if (is_null ($this -> data))
        {
            // Initialize the data property
            $this -> data = array ();
            // Populate the data property with a DB query
            $query = $this -> db -> prepare ('SELECT * FROM footable;');
            if ($query -> execute ())
            {
                $this -> data = $query -> fetchAll ();
            }
        }
        return ($this -> data);
    }

    public function doSomethingWithData ()
    {
        foreach ($this -> getData () as $row)
        {
            // Do processing
        }
    }
}

In this version, you can call doSomethingWithData (and indeed getData) as often as you like, you will never trigger more than one database lookup. Furthermore, if getData and doSomethingWithData are never called, no database lookup is ever done. This will result in a big performance win, as database lookups are expensive and should be avoided where possible.

It does lead to some problems if you're working in a class that can update the database, but it's not hard to work around. If a class makes updates to its state, then your setters can simply be coded so that they null their associated state on success. That way, the data will be refreshed from the database the next time a getter is called.

GordonM
  • 31,179
  • 15
  • 87
  • 129
  • "Best practice tends to favour the correct over the most performant unless the performance is genuinely suffering as a result of it." It's a shame I can only +1 once – adlawson Dec 31 '11 at 17:23
  • But calling getData() internally is considered best practice from your point of view, right? Why is _data defined as protected, for pragmatic reasons? – Roland Franssen Dec 31 '11 at 17:58
  • It's protected as if it was public then external agents could inject anything they wanted into the property with gay abandon, thus completely messing up the internal state of your object. Protected means that it is still available within subclasses for directly manipulation though, so an extending class that needs to do something with the property that its superclass cannot still can. – GordonM Dec 31 '11 at 18:02