Object-Oriented PHP Part 3: Taking Relationships Further

Published 16:41 on 23 August, 2006

Following on from my posts “Object-Oriented Concepts” and “Object-Oriented Javascript”, I’m going to take a look at OOP in PHP.

In “Part 1: Definition” we took a look at defining objects and classes in PHP. In “Part 2: Relationships” we looked at linking our objects and classes together. In part 3 I’m going to add to those relationships by looking at visibility, polymorphism and the scope resolution operator.

Access Modifiers (aka Visibility)

Access modifiers allow you to alter the visibility of any class member. They have long been an important part of OOP and have finally been implemented in PHP as of version 5 - in fact they replace the deprecated var declaration. However, if you’re still a member of the PHP4 posse, don’t fret; there’s a little known suggestion in the PHP manual that can help us to imply private. I’ll cover that in a second.

To define the visibility of a class member we need to prefix it with one of the keywords public, private or protected. Public declared members can be accessed everywhere. Protected limits access to inherited and parent classes (and to the class that defines the item). Private limits visibility only to the class that defines the item.

To illustrate this in action, I’m going to poach a couple of examples directly from the PHP manual. Firstly, let’s take a look at access modifiers applied to class attributes:

class myClass
{
  public $public = 'Public';
  protected $protected = 'Protected';
  private $private = 'Private';

  function printHello()
  {
    echo $this->public;
    echo $this->protected;
    echo $this->private;
  }
}

$obj = new myClass();
echo $obj->public; // Works
echo $obj->protected; // Fatal Error
echo $obj->private; // Fatal Error

// Shows Public, Protected and Private
$obj->printHello();

class myClass2 extends myClass
{
  // We can redeclare the public and
  // protected method, but not private
  protected $protected = 'Protected2';

  function printHello()
  {
    echo $this->public;
    echo $this->protected;
    echo $this->private;
  }
}

$obj2 = new myClass2();
echo $obj->public; // Works
echo $obj2->private; // Undefined
echo $obj2->protected; // Fatal Error

// Shows Public, Protected2, not Private
$obj2->printHello();

Now let’s take a look at access modifiers applied to methods:

class myClass
{
  // Contructors must be public
  public function __construct() { }

  // Declare a public method
  public function myPublic() { }

  // Declare a protected method
  protected function myProtected() { }

  // Declare a private method
  private function myPrivate() { }

  // This is public
  function foo()
  {
    $this->myPublic();
    $this->myProtected();
    $this->myPrivate();
  }
}

$myclass = new myClass;
$myclass->myPublic(); // Works
$myclass->myProtected(); // Fatal Error
$myclass->myPrivate(); // Fatal Error

// Public, Protected and Private work
$myclass->foo();

class myClass2 extends myClass
{
  // This is public
  function foo2()
  {
    $this->myPublic();
    $this->myProtected();
    $this->myPrivate(); // Fatal Error
  }
}

$myclass2 = new myClass2;
$myclass2->myPublic(); // Works

// Public and Protected work, not Private
$myclass2->foo2();

For users of PHP4 there are, unfortunately, no access modifiers available. However, under “Object Aggregation/Composition Functions” in the PHP manual the following is noted:

Note that for the purposes of object aggregation, private elements of a class/object begin with an underscore character ("_"), even though there is not real distinction between public and private class/object elements in PHP.

With this in mind, I usually declare private members with an underscore character regardless of whether it’s for aggregation or not.

Polymorphism

An object-oriented programming language must support polymorphism; meaning different classes can have different behaviours for the same attribute or method. This can best be illustrated with the same example I used in my “Object-Oriented Concepts” post:

class formElement
{
  var $id;
  var $name;
  var $class;

  function getHtml()
  {
    // returns generic form element HTML
    // using heredoc to save on escapes
    return <<<EOD

<input id="{$this->id}" name="{$this->name}" class="{$this->class}"/>

EOD;
  }
}

class textarea extends formElement
{
  var $cols;
  var $rows;

  function getHtml()
  {
    // returns generic form element HTML
    // using heredoc to save on escapes
    return <<<EOD

<textarea id="{$this->id}" name="{$this->name}" class="{$this->class}" cols="{$this->cols}" rows="{$this->rows}"></textarea>

EOD;
  }
}

As you can see in the example, both classes have the method “getHtml” but the “textarea” class is a subclass of the “formElement” class. This results in the “getHtml” method being overloaded. Overloading is a good example of polymorphism in action - an object-oriented language must support polymorphism in order to know which “getHtml” method applies to which object.

Polymorphism, at its most basic, describes the fact that a given function may have different specifications, depending on the object to which it is applied.

Scope Resolution Operator

The Scope Resolution Operator, “::” (double-colon or Paamayim Nekudotayim - that’s double-colon in Hebrew - don’t ask me; ask the Zend team!), allows us to access a method or an attribute from an uninstantiated class. This is incredibally useful if you wish to create a class of functions but don’t want to instantiate an object of that class. Here’s an example class:

class validation
{
  function isValidEmail($str_data)
  {
    return (!eregi("^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,3})$", $Email)) ? false : true;
  }
  
  function isLessThan($str_data, $str_compare)
  {
    return ($str_data < $str_compare) ? true : false;
  }
  
  function isGreaterThan($str_data, $str_compare)
  {
    return ($str_data > $str_compare) ? true : false;
  }
}

Now if we want to check an email address, we can access the isValidEmail method, without instantiating the class, like so:

$bln_valid = validation::isValidEmail('big.cheese@dairy.com');

The PHP documentation takes this one step further by illustrating how the scope resolution operator can benefit polymorphism:

class A {
   function example() {
       echo "I am the original function A::example().
\n"; } } class B extends A { function example() { echo "I am the redefined function B::example().
\n"; A::example(); } } // there is no object of class A. // this will print // I am the original function A::example().
A::example(); // create an object of class B. $b = new B; // this will print // I am the redefined function B::example().
// I am the original function A::example().
$b->example();

In this example you can see that class B overloads the example() method. Since this is the case, the original example() method is “shadowed” and is therefore no longer available - that is, however, unless we use the scope resolution operator to access it. This basically allows us to extend the functionality of an overloaded method in a child class. Isn’t that just incredibally useful?

parent

PHP takes this particular use of the scope resolution operator one step further by allowing us to reference our parent class without having to use its literal name. To do this, we use the parent special name like so:

class A {
   function example() {
       echo "I am A::example() and provide basic functionality.
\n"; } } class B extends A { function example() { echo "I am B::example() and provide additional functionality.
\n"; parent::example(); } } $b = new B; // This will call B::example(), which will in turn call A::example(). $b->example();

By using this method you are avoiding using the name of your parent class in too many places - thus you will only need to change the class name in the extends clause.

PHP5 and the Scope Resolution Operator

In PHP5 any methods or attributes you want to access with the scope resolution operator must be declared as static. The static declaration must appear after the visibility declaration. However, if no visibility declaration is included (PHP4 compatibility), the member will be treated as public:

class monkey
{
  public static $int_bananas = 6;

  public static function addBananas($int_bananas)
  {
    // Add the new bananas to our
    // int_bananas attribute. I'll explain the self
    // keyword a little further below...
    self::$int_bananas += (int) $int_bananas;
  }
}

Any attributes you declare as static are subsequently unavailable within instantiated objects. Static methods, however, are available but, since they are callable without an object instance being created, cannot use the $this pseudo variable.

To reference a static member from within the same class, we can use the self special name; as seen in the previous example.

Static method calls are resolved at compile time. This means that, when we’re using an explicit class name, the method is already identified completely and, subsequently, no inheritance rules apply. This is also the case when using the self special name.

Static properties cannot be accessed through the object using the -> namespace separator.

Calling non-static methods statically generates an E_STRICT level warning.

Summary

As you can see from the above examples, polymorphism and visibility extend the relationships between your objects and classes ten-fold. In fact, you’ll find them invaluable once you start taking them into account during the code planning phase of your next big project. The scope resolution operator will allow you to take polymorphism once step further or to simplify relationships that you may be over-complicating (I’ve done that more than once, believe me).

Well that’s pretty much it for all you PHP4 developers out there - unfortunately the complex functionality I’m going to look at in the next post is limited to the updated object model in PHP5. However, I’d recommend you read it simply to improve your understanding of object-oriented systems. Of course, it’ll also help you make the decision to upgrade (or to keep pestering your boss to upgrade).

Click here to continue on to “Part 4: PHP5’s Improved Features”…