PHP Object Attributes

  • published on December 21st, 2007

    In this article we’re going to draw inspiration from Ruby’s attr_accessor method in an attempt to create dynamic getter and setter methods for PHP classes. Let’s start by taking a look at a similar way in which we can use the two languages to create a simple Book class.

    Accessing Attributes

    We can directly assign and access public data members in PHP, but Ruby objects have no public data members. We can simulate a similar behavior in Ruby using the attr_accessor method.

    PHP

    class Book
    {
        public $author;
    }
     
    $book = new Book;
    $book->author = 'jim';
    print $book->author;
    // => jim

    Ruby

    class Book
      attr_accessor :author
    end
     
    book = Book.new
    book.author = 'jim'
    print book.author
    # => jim

    While it appears that Ruby is using a public attribute, this is simply because parentheses are optional in Ruby. We can call the same methods like this:

    Ruby

    book.author=('jim')
    print book.author()

    Taking a look at the class definition, one might infer that Ruby’s attr_accessor class method is creating a public attribute. It is actually doing something quite different. When we invoke this method, we dynamically create the author() and author=() instance methods which access and assign the @author instance variable in our object. This gives us some flexibility to change the implementation of these methods later on if we choose.

    The Problem with Public

    Imagine we want our class to capitalize the author name when we assign it. In Ruby, we could implement this pretty easily without breaking the existing API. We can explicitly define the accessor method ourselves.

    Ruby

    class Book
      attr_accessor :author
     
      def author=(author)
        @author = author.capitalize
      end
    end
     
    book = Book.new
    book.author = 'jim'
    print book.author
    # => Jim

    If we were to try and do the same thing in PHP with public data members, we immediately see a problem. We can’t change the implementation of author assignment. We are assigning the value directly, and have no encapsulation within the object.

    Getter and Setters

    There is a point where you basically give up on public attributes for all but the most basic data structures. They’re just too inflexible, with the preferred alternative being protected or private members with getter and setter methods:

    PHP

    class Book
    {
        protected $_author;
     
        public function setAuthor($author) 
        {
            $this->_author = ucfirst($author);
        }
     
        public function getAuthor()
        {
            return $this->_author;
        }
    }
     
    $book = new Book;
    $book->setAuthor('jim');
    print $book->getAuthor();
    // => Jim

    This class definition accomplishes the same thing as the above Ruby class definition. By using methods to access the author, we can easily abstract any details within our class without any need to change the class API in the future.

    Better Access using Magic

    The PHP example is a bit more verbose than the Ruby, and would become even more so for each attribute that we add. Using Ruby as an example, we can actually come up with a reasonably elegant solution to the repetitive getter/setter methods using PHP’s __get and __set magic methods. Let’s say that we also want a title and page count for our class. The Ruby definition would look like this:

    Ruby

    class Book
      attr_accessor :author, :title, :pages
     
      def author=(author)
        @author = author.capitalize
      end
    end
     
    book = Book.new
    book.author = 'jim'
    book.title  = 'Wandering in the Desert'
     
    print "#{book.author} : #{book.title}"
    # => Jim : Wandering in the Desert

    Wouldn’t it be nice if if we could do something like this in PHP?

    PHP

    class Book extends AttrObject
    {
        protected $_author;
        protected $_title;
        protected $_pages;
     
        public function __construct()
        {
            $this->attrAccessor('author', 'title', 'pages');
        }
     
        public function setAuthor($author)
        {
            $this->_author = ucfirst($author);
        }
    }
     
    $book = new Book;
    $book->author = 'jim';
    $book->title  = 'Wandering in the Desert'
     
    print "$book->author : $book->title";
    // => Jim : Wandering in the Desert

    This syntax simplifies our class by eliminating redundant getter and setter methods. The custom AttrObject class defines three methods. The attrAccessor() method creates an attribute on the instance that can be both read and written. Two other variations exist: attrReader() creates attributes that can only be read, and attrWriter() creates attributes that can only be written.

    Overriding Attributes

    While it may appear that we’re calling public attributes, these attributes remain protected. AttrObject works by using the missing property handlers __get() and __set(). When one of our attributes is accessed, it first scans for a method like getAuthor() or setAuthor() on the object. If not found, it looks for a protected property like $_author. If that is also not found, it searches for a method called _get() or _set(). Finally, an error is found if nothing is available to satisfy the conditions.

    You can download the class source code here:
    attr-code.tgz for Unix
    attr-code.zip for Windows

    The advantages of the example are clear when the implementation of these attributes need to change. The attributes promote encapsulation by allowing the implementation to change while the interface remains the same.

3 comments

  • comment by Michel de Lange 4 Jan 08

    Code note, demo/demo.php: you might want to change require_once dirname(__FILE__).’/lib/AttrObject.php’ to require_once dirname(__FILE__).’/../lib/AttrObject.php’.

  • comment by Mike 4 Jan 08

    That’s been fixed, thanks.

  • comment by Maledictus 19 Feb 08

    You should use attr_reader instead of attr_accessor if you want to write your own setter.

Post a comment


We welcome your participation but please note we reserve the right to remove any comments that we think are not relevant or do not contribute to the discussion.