Object Property Visibility and Magic Methods

Code Naked

  • Homepage
  • About
  • Contact

Jun 8: Object Property Visibility and Magic Methods

I often thought that one of the great features of OOP in PHP 5 was the addition of the public, protected and private keywords. Now, I find that they are total pain in the ass when used incorrectly. I was just reading the article on DevShed about protecting class data with member visibility and I was reminded of an email that I had sent out to the PHP developers that I work with. As usual, there was not one response to the idea of using magic methods rather than fifty eleven accessors and mutators. I personally use the magic __get and __set methods all of the time but I'll get to that in a minute.
Let me first ask you, what's the difference between these two methods of accessing an objects "title" property:
// Using public visibility to access property
echo $book->title;

// Using an accessor (get) method to access property
echo $book->getTitle();
I'll tell you what the difference is, the first one is simple and the other sucks. For the latter form, not only do we have to mentally parse the name of the accessor method, we also have to ignore the parenthesis. All of the junk around the name of the property makes it much more unclear as to what's going on. As a developer that doesn't like to type semi-colons, don't even get me started on the empty parenthesis.

The thing that bugs me about accessors is that ninety percent of the time you are just writing a weak getter for the property anyway. Why bother to "hide" the property if you are just going to give everyone access to it anyway? Maybe you are one of those sickos that doesn't feel like they are doing anything unless they are calling functions.
The thing is, I am actually not against member visibility when it's warranted. Like all things, there is a right time and place for this tool to be used. Typing out (or generating) fifty lines of accessors and setters that do nothing but give public access to your properties is plain retarded in my opinion.

That being said, if you have a static instance property in a singleton then you definitely want to make sure that nothing outside of your object is going to be able to mess with it. Now, in the latter case, I am sure that the developer would just slap a private on the property and call it a day. For the love of everything maintainable don't set anything to private unless you really mean it. Developers too often place private visibility on properties and methods for that matter that they really want to be protected. You should treat private as analogous to the final keyword. Declaring a class as final means that it can no longer be inherited from. Private does essentially the same thing for properties and methods. Don't close the door on inheritance unless you really mean it.

One of the arguments for using visibility is that without it every class becomes a fancy array. I actually agree with this statement. What's the point of working so hard to get your objects separated out into distinct classes that model your domain if you are going to let anyone walk all over them? This is why I use the magic methods __get and __set to provide access to my properties. Take a look at this basic Book class:
class Book
{
  protected $title;
  protected $fiction;
}
You can see that I have declared the properties as protected to discourage unfettered access but also allow them to be modified by classes extending Book. So how are we going to access them you ask? Well it's not by writing two accessors and two mutators that's for sure. Check this out:
class Book
{
    /**
     * @var string $title The title of the book
     */
    protected $title;

    /**
     * @var boolean $fiction Is the book a fiction piece
     */
    protected $fiction;


    /**
     * Determine if this object has the requested property
     *
     * @param mixed $property
     */
    protected function hasProperty($property)
    {
        return array_key_exists($property, get_object_vars($this));
    }


    /**
     * Magic method to access this objects properties
     *
     * @param string $property
     */
    public function __get($property)    
    {
        if ($this->hasProperty($property)) {
            return $this->$property;
        }

        throw new InvalidPropertyException("Property $property does not exist");
    }


    /**
     * Magic method to set this objects properties
     *
     * @param string $property
     * @param mixed $value
     */
    public function __set($property, $value)
    {
        if ($this->hasProperty($property)) {
            $this->$property = $value;
            return $value;
        }

        throw new InvalidPropertyException("Property $property does not exist");
    }
}
So we can now get or set and declared property of our object using the same syntax as if we had made the properties public. To make things even cleaner I like to extract the magic methods into a DomainObject supertype.
class DomainObject
{
    /**
     * Determine if this object has the requested property
     *
     * @param mixed $property
     */
    protected function hasProperty($property)
    {
        return array_key_exists($property, get_object_vars($this));
    }


    /**
     * Magic method to access this objects properties
     *
     * @param string $property
     */
    public function __get($property)    
    {
        $methodName = 'get' . $property;

        if (method_exists($this, $methodName)) {
          return $this->$methodName();
        }

        if ($this->hasProperty($property)) {
            return $this->$property;
        }

        throw new InvalidPropertyException("Property $property does not exist");
    }


    /**
     * Magic method to set this objects properties
     *
     * @param string $property
     * @param mixed $value
     */
    public function __set($property, $value)
    {
        $methodName = 'set' . $property;

        if (method_exists($this, $methodName)) {
          return $this->$methodName($value);
        }

        if ($this->hasProperty($property)) {
            $this->$property = $value;
            return $value;
        }

        throw new InvalidPropertyException("Property $property does not exist");
    }
}


class Book extends DomainObject
{
    /**
     * @var string $title The title of the book
     */
    protected $title;

    /**
     * @var boolean $fiction Is the book a fiction piece
     */
    protected $fiction;
}
To me the new Book model communicates in a very clear manner what it is. By not immediately jumping on the getter and setter wagon, we can now look at this model without the clutter. With one final change we have a very nice interface:
class Book extends DomainObject
{
    /**
     * @var string $title The title of the book
     */
    protected $title;

    /**
     * @var boolean $isFiction Is the book a fiction piece
     */
    protected $isFiction;
}

// Create a new book and set its properties
$book = new Book();
$book->title = 'War and Peace';
$book->isFiction = true;

// Display all fiction books
foreach ($books as $book) {
    if($book->isFiction) {
        echo $book->title,'
'; } }


Another nice feature is that if we want to create a dedicated setter/getter for a particular property, we just have to add the method the same as we used to. Because the method is explicitly written, it's much more clear that there is a special case for the method that was defined.
Posted by Matthew Purdon in PHP Comments: (0) Trackbacks: (0)

Trackbacks
Trackback specific URI for this entry

No Trackbacks

Comments
Display comments as (Linear | Threaded)

No comments


Add Comment

Standard emoticons like :-) and ;-) are converted to images.
 
 

Subscribe

Archives

  • March 2010 (0)
  • February 2010 (1)
  • January 2010 (4)
  • December 2009 (6)
  • Recent...
  • Older...

Categories

  • XML Technology
  • XML Databases
  • XML MySQL
  • XML Software Development
  • XML Being a Contractor
  • XML Client Side
  • XML PHP
  • XML State of the Art


All categories

Blog Administration

Open login screen

Recommended Reading

Amazon.com: The Nomadic Developer: Surviving and Thriving in the World of Technology Consulting (9780321606396): Aaron Erickson: Books
Amazon.com: Refactoring: Improving the Design of Existing Code (9780201485677): Martin Fowler, Kent Beck, John Brant, William Opdyke, Don Roberts: Books
Amazon.com: Domain-Driven Design: Tackling Complexity in the Heart of Software (0076092019565): Eric Evans: Books
Amazon.com: Patterns of Enterprise Application Architecture (0076092019909): Martin Fowler: Books

Feedburner

Numeric Feedburner ID Required!




 

Layout by Andreas Viklund | Serendipity template by Carl