Object Oriented Programming (OOP) in PHP

Object-oriented features have been continually added to PHP with each new version. Starting with version 5, PHP has had a full, class-based object-oriented programming support, meaning that it facilitates the creation of objects through the use of classes and class declarations. Classes are essentially “blueprints” for creating instances of objects. An object is an entity that is characterized by identity, state and behavior. The identity of an object is an aspect that distinguishes it from other objects. The variables and values that a variable takes on within an object is its state. Typically the variables that belong to an object are referred to as member variables. Finally, an object may also have functions that operate on the data of an object. In the context of object-oriented programming, a function that belongs to an object is referred to as a member method. A class declaration specifies the member variables and member methods that belong to instances of the class. We discuss how to create and use instances of a class below. However, to begin, let’s define a class that models a student by defining member variables to support a first name, last name, a unique identifier, and GPA.

To declare a class, we use the class keyword. Inside the class (denoted by curly brackets), we place any code that belongs to the class. To declare member variables in a class, we specify the variable names and their visibility inside the class, but outside any methods in the class.

class Student {

    //member variables:
    private $firstName;
    private $lastName;
    private $id;
    private $gpa;

}

To organize code, it is common practice to place class declarations in separate files with the same name as the class. For example, this Student class declaration would be placed in a file named Student.php and included in any other script files that utilized the class.

Data Visibility

Recall that encapsulation involves not only the grouping of data, but the protection of data. The class declaration above achieves the grouping of data. To provide for the protection of data, PHP defines several visibility keywords that specify what segments of code can “see” the variables. Visibility in this context determines whether or not a segment of code can access and/or modify the variable’s value. PHP defines three levels of visibility using they keywords public, protected and private. Each of these keywords can be applied to both member variables and member methods.

  • public: This is the least restrictive visibility level and makes the member variable visible to any code segment.
  • protected: This is a bit more restrictive and makes it so that the member variable is only visible to the code in the same class, or any subclass of the class.1
  • private: this is the most restrictive visibility level, private member variables are only visible to instances of the class itself.

Table below summarizes these four keywords with respect to their access levels. It is important to understand that protection is in the context of encapsulation and does not involve protection in the sense of “security.” Protection is this context is a design principle. Limiting the access of variables only affects how the rest of the code base interacts with our class and its data.

\[\begin{array}{|c|c|c|c|} \hline \text { Modifier } & \text { Class } & \text { Subclass } & \text { World } \\ \hline \hline \text { public } & \mathrm{Y} & \mathrm{Y} & \mathrm{Y} \\ \hline \text { protected } & \mathrm{Y} & \mathrm{Y} & \mathrm{N} \\ \hline \text { private } & \mathrm{Y} & \mathrm{N} & \mathrm{N} \\ \hline \end{array}\]

Table: PHP Visibility Keywords & Access Levels

In general, it is best practice to make member variables private and control access to them via accessor and mutator methods (see below) unless there is a compelling design reason to increase their visibility.

Methods

The third aspect of encapsulation involves the grouping of methods that act on an object’s data. Within a class, we can declare member methods using the syntax we’re already familiar with. We declare a member method by using the keyword function and providing a signature and body. We can use the same visibility keywords as with member variables in order allow or restrict access to the methods. With methods, visibility and access determine whether or not the method may be invoked.

We add to our example by providing two public methods that compute and return a result on the member variables. We also use javadoc-style comments to document each member method.

 class Student {

   //member variables:
   private $firstName;
   private $lastName;
   private $id;
   private $gpa;

   /**
    * Returns a formatted String of the Student's
    * name as Last, First.
    */
   public function getFormattedName() {
     return $this -> lastName . ", " . $this -> firstName;
   }

   /**
    * Scales the GPA, which is assumed to be on a
    * 4.0 scale to a percentage.
    */
   public function getGpaAsPercentage() {
     return $this -> gpa / 4.0;
   }
 }

There is some new syntax in the example above. In the member methods, we need a way to refer to the instance’s member variables. The keyword $this is used to refer to the instance, this is known as open recursion.

When an instance of a class is created, for example:

$s = new Student();

the reference variable $s is how we can refer to it. This variable, however, exists outside the class. Inside the class, we need a way to refer to the instance itself. Since we don’t have a variable inside the class to reference the instance itself, PHP provides the keyword $this in order to do so. Then, to access the member variables we use the arrow operator more below) and reference the member variable via its identifier but with no dollar sign.

Note: You can use the syntax $this->$foo but it will assume that $foo is a string that contains the name of another variable, for example, if $foo = "firstName"; then $this->$foo would resolve to the instance’s $firstName variable. This is useful if your object has been dynamically created by adding variables at runtime that were not part of the original class declaration.

Accessor & Mutator Methods

Since we have made all the member variables private, no code outside the class may access or modify their values. It is generally good practice to make member variables private to restrict access. However, if we still want code outside the object to access or mutate (that is, change) the variable values, we can define accessor and mutator methods (or simply getter and setter methods) to facilitate this.

Each getter method returns the value of the instance’s variable while each setter method takes a value and sets the instance’s variable to the new value. It is common to name each getter/setter by prefixing a get and set to the variable’s name using lower camel casing. For example:

public function getFirstName() {
    return $this->firstName;
}

public function setFirstName($firstName) {
    $this->firstName = $firstName;
}

One advantage to using getters and setters (as opposed to naively making everything public) is that you can have greater control over the values that your variables can take. For example, we may want to do some data validation by rejecting null values or invalid values. For example:

public function setFirstName($firstName) {
    if($firstName === null) {
        throw new Exception("names cannot be null");
    } else {
        $this->firstName = $firstName;
    }
}

public function setGpa($gpa) {
    if($gpa < 0.0 || $gpa > 4.0) {
        throw new Exception("GPAs must be in [0, 4.0]");
    } else {
        $this->gpa = $gpa;
    }
}

Controlling access of member variables through getters and setters is good encapsulation. Doing so makes your code more predictable and more testable. Making your member variables public means that any piece of code can change their values. There is no way to do validation or prevent bad values.

In fact, it is good practice to not even have setter methods. If the value of member variables cannot be changed, it makes the object immutable. Immutability is a nice property because it makes instances of the class thread-safe. That is, we can use instances of the class in a multithreaded program without having to worry about threads changing the values of the instance on one another.

Constructors

If we make the (good) design decision to make our class immutable, we still need a way to initialize the values. This is where a constructor comes in. A constructor is a special method that specifies how an object is constructed. With built-in variable types such as an numbers or strings, PHP “knows” how to interpret and assign a value. However, with user-defined objects such as our Student class, we need to specify how the object is created.

Just as with functions outside of classes, PHP does not support function overloading inside classes. That is, you can have one and only one function with a given identifier (name). Thus, there is only one possible constructor. Moreover, PHP reserves the name __construct for the constructor method. The two underscores are a naming convention used by PHP to denote “magic methods” that are reserved and have a special purpose in the language. Further, magic methods must be made public . Some magic methods provide default behavior while others do not. For example, if you do not define a constructor method, the default behavior will be to create an object whose member variables all have null values.

The following constructor allows a user to construct an instance of our Student instance and specify all four member variables.

public function __construct($firstName, $lastName, $id, $gpa) {
    $this->firstName = $firstName;
    $this->lastName = $lastName;
    $this->id = $id;
    $this->gpa = $gpa;
}

Though we cannot define multiple constructors, we can use the default value feature of PHP functions to allow a user to call our constructor with a different number of parameters. For example:

public function __construct($firstName, $lastName, $id = 0, $gpa = 0.0) {
    $this->firstName = $firstName;
    $this->lastName = $lastName;
    $this->id = $id;
    $this->gpa = $gpa;
}

new Keyword

Once we have defined our class and its constructors, we can create and use instances of it. With regular variables, we simply need to assign them to an instance of an object and their type will dynamically change to match. To create new instances, we invoke a constructor by using the new keyword and providing arguments to the constructor.

$s = new Student("Alan", "Turing", 1234, 3.4);
$t = new Student("Margaret", "Hamilton", 4321, 3.9);

The process of creating a new instance by invoking a constructor is referred to as instantiation. Once instances have been instantiated, they can be used by invoking their methods via the same arrow operator we used to access member variables. Outside the class, however this will only work if the member method is public.

print $t->getFormattedName() . "\n";

if($s->getGpa() < $t->getGpa()) {
    print $t->getFirstName() . " has a better GPA\n";
}

__toString() Keyword

Another useful magic method is the __toString() method which returns a string representation of the object. Unlike the constructor method, there is no default behavior with the __toString() method. If you do not define this function, it cannot be used (and any attempts to do so will result in a fatal error). We can define the method to return the values of all or some of the class’s variables in whatever format we want.

public function __toString() {
    return sprintf("%s, %s (ID = %d); %.2f",
            $this->lastName,
            $this->firstName,
            $this->id,
            $this->gpa);
}

This would return a string containing something similar to:

"Hamilton, Margaret (ID = 4321); 3.90"

Composition

Another important concept when designing classes is composition. Composition is a mechanism by which an object is made up of other objects. One object is said to “own” an instance of another object.

To illustrate the importance of composition, we could extend the design of our Student class to include a date of birth. However, a date of birth is also made up of multiple pieces of data (a year, a month, a date, and maybe even a time and/or locale). We could design our own date/time class to model this, but its generally best to use what the language already provides. PHP 5.2 introduced the DateTime object in which there is a lot of functionality supporting the representation and comparison of dates and time. We can take this concept further and have our own user-defined classes own instances of each other. For example, we could define a Course class and then update our Student class to own a collection of Course objects representing a student’s class schedule (this type of collection ownership is sometimes referred to as aggregation rather than composition).

Both of these design updates beg the question: who is responsible for instantiating the instances of $dateOfBirth and the $schedule? Should we force the “outside” user of our Student class to build a DateTime instance and pass it to a constructor? Should we allow the outside code to simply provide us a date of birth as a string and make the constructor responsible for creating the proper DateTime instance? Do we require that a user create a complete array of Course instances and provide it to the constructor at instantiation?

A more flexible approach might be to allow the construction of a Student instance without having to provide a course schedule. Instead, we could add a method that allowed the outside code to add a course to the student. For example:

public function addCourse($c) {
    $this->schedule[] = $c;
}

This adds some flexibility to our object, but removes the immutability property. Design is always a balance and compromise between competing considerations.

Complete OOP Example in PHP

The completed PHP Student class is source code is below.

<?php
class Student {

  private $firstName;
  private $lastName;
  private $id;
  private $gpa;
  private $dateOfBirth;
  private $schedule;

  public
  function __construct($firstName, $lastName, $id = 0, $gpa = 0.0,
    $dateOfBirth = null, $schedule = array()) {
    $this->firstName = $firstName;
    $this->lastName = $lastName;
    $this->id = $id;
    $this->gpa = $gpa;
    $this->dateOfBirth = new DateTime($dateOfBirth);
    $this->schedule = $schedule;
  }

  public function __toString() {
    return $this->getFormattedName() . " born ". $this->dateOfBirth->format("Y-m-d");
  }

  /**
   * Returns a formatted String of the Student's
   * name as Last, First.
   */
  public function getFormattedName() {
    return $this->lastName . ", " . $this->firstName;
  }

  /**
   * Scales the GPA, which is assumed to be on a
   * 4.0 scale to a percentage.
   */
  public function getGpaAsPercentage() {
    return $this->gpa / 4.0;
  }

  public function getFirstName() {
    return $this->firstName;
  }

  public function getLastName() {
    return $this->lastName;
  }

  public function getId() {
    return $this->id;
  }

  public function getGpa() {
    return $this->gpa;
  }

  public function addCourse($c) {
    $this->schedule[] = $c;
  }
}
?> 

Licenses and Attributions


Speak Your Mind