Chapter 6. Objects

In this chapter you’ll learn how to define, create, and use objects in PHP. Object-oriented programming (OOP) opens the door to cleaner designs, easier maintenance, and greater code reuse. OOP has proven so valuable that few today would dare to introduce a language that wasn’t object-oriented. PHP supports many useful features of OOP, and this chapter shows you how to use them, covering basic OOP concepts as well as advanced topics such as introspection and serialization.

Objects

Object-oriented programming acknowledges the fundamental connection between data and the code that works on it, and lets you design and implement programs around that connection. For example, a bulletin-board system usually keeps track of many users. In a procedural programming language, each user is represented by a data structure, and there would probably be a set of functions that work with those data structures (to create the new users, get their information, etc.). In an OOP language, each user is represented by an object—a data structure with attached code. The data and the code are still there, but they’re treated as an inseparable unit. The object, as a union of code and data, is the modular unit for application development and code reuse.

In this hypothetical bulletin-board design, objects can represent not just users but also messages and threads. A user object has a username and password for that user, and code to identify all the messages by that author. A message object knows which thread it belongs to and has code to post a new message, reply to an existing message, and display messages. A thread object is a collection of message objects, and it has code to display a thread index. This is only one way of dividing the necessary functionality into objects, though. For instance, in an alternate design, the code to post a new message lives in the user object, not the message object.

Designing object-oriented systems is a complex topic, and many books have been written on it. The good news is that however you design your system, you can implement it in PHP. Let’s begin by introducing some of the key terms and concepts you’ll need to know before diving into this programming approach.

Terminology

Every object-oriented language seems to have a different set of terms for the same old concepts. This section describes the terms that PHP uses, but be warned that in other languages these terms may have other meanings.

Let’s return to the example of the users of a bulletin board. You need to keep track of the same information for each user, and the same functions can be called on each user’s data structure. When you design the program, you decide the fields for each user and come up with the functions. In OOP terms, you’re designing the user class. A class is a template for building objects.

An object is an instance (or occurrence) of a class. In this case, it’s an actual user data structure with attached code. Objects and classes are a bit like values and data types. There’s only one integer data type, but there are many possible integers. Similarly, your program defines only one user class but can create many different (or identical) users from it.

The data associated with an object are called its properties. The functions associated with an object are called its methods. When you define a class, you define the names of its properties and give the code for its methods.

Debugging and maintenance of programs is much easier if you use encapsulation. This is the idea that a class provides certain methods (the interface) to the code that uses its objects, so the outside code does not directly access the data structures of those objects. Debugging is thus easier because you know where to look for bugs—the only code that changes an object’s data structures is within the class—and maintenance is easier because you can swap out implementations of a class without changing the code that uses the class, as long as you maintain the same interface.

Any nontrivial object-oriented design probably involves inheritance. This is a way of defining a new class by saying that it’s like an existing class, but with certain new or changed properties and methods. The original class is called the superclass (or parent or base class), and the new class is called the subclass (or derived class). Inheritance is a form of code reuse—the superclass code is reused instead of being copied and pasted into the subclass. Any improvements or modifications to the superclass are automatically passed on to the subclass.

Accessing Properties and Methods

Once you have an object, you can use the -> notation to access methods and properties of the object:

$object->propertyname $object->methodname([arg, ... ])

For example:

echo "Moana is {$moana->age} years old.\n"; // property access
$moana->birthday(); // method call
$moana->setAge(21); // method call with arguments

Methods act the same as functions (only specifically to the object in question), so they can take arguments and return a value:

$clan = $moana->family("extended");

Within a class’s definition, you can specify which methods and properties are publicly accessible and which are accessible only from within the class itself using the public and private access modifiers. You can use these to provide encapsulation.

You can use variable variables with property names:

$prop = 'age';
echo $moana->$prop;

A static method is one that is called on a class, not on an object. Such methods cannot access properties. The name of a static method is the class name followed by two colons and the function name. For instance, this calls the p() static method in the HTML class:

HTML::p("Hello, world");

When declaring a class, you define which properties and methods are static using the static access property.

Once created, objects are passed by reference—that is, instead of copying around the entire object itself (a time- and memory-consuming endeavor), a reference to the object is passed around instead. For example:

$f = new Person("Pua", 75);

$b = $f; // $b and $f point at same object
$b->setName("Hei Hei");

printf("%s and %s are best friends.\n", $b->getName(), $f->getName());
Hei Hei and Hei Hei are best friends.

If you want to create a true copy of an object, you use the clone operator:

$f = new Person("Pua", 35);

$b = clone $f; // make a copy
$b->setName("Hei Hei");// change the copy

printf("%s and %s are best friends.\n", $b->getName(), $f->getName());
Pua and Hei Hei are best friends.

When you use the clone operator to create a copy of an object and that class declares the __clone() method, that method is called on the new object immediately after it’s cloned. You might use this in cases where an object holds external resources (such as file handles) to create new resources, rather than copying the existing ones.

Declaring a Class

To design your program or code library in an object-oriented fashion, you’ll need to define your own classes, using the class keyword. A class definition includes the class name and the properties and methods of the class. Class names are case-insensitive and must conform to the rules for PHP identifiers. Among others, the class name stdClass is reserved. Here’s the syntax for a class definition:

class classname [ extends baseclass ] [ implements interfacename ,
 [interfacename, ... ] ] {
 [ use traitname, [ traitname, ... ]; ]

 [ visibility $property [ = value ]; ... ]

 [ function functionname (args) [: type ] {
 // code
 }
 ...
 ]
}

Declaring Methods

A method is a function defined inside a class. Although PHP imposes no special restrictions, most methods act only on data within the object in which the method resides. Method names beginning with two underscores (__) may be used in the future by PHP (and are currently used for the object serialization methods __sleep() and __wakeup(), described later in this chapter, among others), so it’s recommended that you do not begin your method names with this sequence.

Within a method, the $this variable contains a reference to the object on which the method was called. For instance, if you call $moana->birthday(), inside the birthday() method, $this holds the same value as $moana. Methods use the $this variable to access the properties of the current object and to call other methods on that object.

Here’s a simple class definition of the Person class that shows the $this variable in action:

class Person {
 public $name = '';

 function getName() {
 return $this->name;
 }

 function setName($newName) {
 $this->name = $newName;
 }
}

As you can see, the getName() and setName() methods use $this to access and set the $name property of the current object.

To declare a method as a static method, use the static keyword. Inside of static methods the variable $this is not defined. For example:

class HTMLStuff {
 static function startTable() {
 echo "<table border=\"1\">\n";
 }

 static function endTable() {
 echo "</table>\n";
 }
}

HTMLStuff::startTable();
 // print HTML table rows and columns
HTMLStuff::endTable();

If you declare a method using the final keyword, subclasses cannot override that method. For example:

class Person {
 public $name;

 final function getName() {
 return $this->name;
 }
}

class Child extends Person {
 // syntax error
 function getName() {
 // do something
 }
}

Using access modifiers, you can change the visibility of methods. Methods that are accessible outside methods on the object should be declared public; methods on an instance that can be called only by methods within the same class should be declared private. Finally, methods declared as protected can be called only from within the object’s class methods and the class methods of classes inheriting from the class. Defining the visibility of class methods is optional; if a visibility is not specified, a method is public. For example, you might define:

class Person {
 public $age;

 public function __construct() {
 $this->age = 0;
 }

 public function incrementAge() {
 $this->age += 1;
 $this->ageChanged();
 }

 protected function decrementAge() {
 $this->age -= 1;
 $this->ageChanged();
 }

 private function ageChanged() {
 echo "Age changed to {$this->age}";
 }
}

class SupernaturalPerson extends Person {
 public function incrementAge() {
 // ages in reverse
 $this->decrementAge();
 }
}

$person = new Person;
$person->incrementAge();
$person->decrementAge(); // not allowed
$person->ageChanged(); // also not allowed

$person = new SupernaturalPerson;
$person->incrementAge(); // calls decrementAge under the hood

You can use type hinting (described in Chapter 3) when declaring a method on an object:

class Person {
 function takeJob(Job $job) {
 echo "Now employed as a {$job->title}\n";
 }
}

When a method returns a value, you can use type hinting to declare the method’s return value type:

class Person {
 function bestJob(): Job {
 $job = Job("PHP developer");

 return $job;
 }
}

Declaring Properties

In the previous definition of the Person class, we explicitly declared the $name property. Property declarations are optional and are simply a courtesy to whomever maintains your program. It’s good PHP style to declare your properties, but you can add new properties at any time.

Here’s a version of the Person class that has an undeclared $name property:

class Person {
 function getName() {
 return $this->name;
 }

 function setName($newName) {
 $this->name = $newName;
 }
}

You can assign default values to properties, but those default values must be simple constants:

public $name = "J Doe"; // works
public $age = 0; // works
public $day = 60 * 60 * hoursInDay(); // doesn't work

Using access modifiers, you can change the visibility of properties. Properties that are accessible outside the object’s scope should be declared public; properties on an instance that can be accessed only by methods within the same class should be declared private. Finally, properties declared as protected can be accessed only by the object’s class methods and the class methods of classes inheriting from the class. For example, you might declare a user class:

class Person {
 protected $rowId = 0;

 public $username = 'Anyone can see me';

 private $hidden = true;
}

In addition to properties on instances of objects, PHP allows you to define static properties, which are variables on an object class, and can be accessed by referencing the property with the class name. For example:

class Person {
 static $global = 23;
}

$localCopy = Person::$global;

Inside an instance of the object class, you can also refer to the static property using the self keyword, like echo self::$global;.

If a property is accessed on an object that doesn’t exist, and if the __get() or __set() method is defined for the object’s class, that method is given an opportunity to either retrieve a value or set the value for that property.

For example, you might declare a class that represents data pulled from a database, but you might not want to pull in large data values—such as Binary Large Objects (BLOBs)—unless specifically requested. One way to implement that, of course, would be to create access methods for the property that read and write the data whenever requested. Another method might be to use these overloading methods:

class Person {
 public function __get($property) {
 if ($property === 'biography') {
 $biography = "long text here..."; // would retrieve from database

 return $biography;
 }
 }

 public function __set($property, $value) {
 if ($property === 'biography') {
 // set the value in the database
 }
 }
}

Inheritance

To inherit the properties and methods from another class, use the extends keyword in the class definition, followed by the name of the base class:

class Person {
 public $name, $address, $age;
}

class Employee extends Person {
 public $position, $salary;
}

The Employee class contains the $position and $salary properties, as well as the $name, $address, and $age properties inherited from the Person class.

If a derived class has a property or method with the same name as one in its parent class, the property or method in the derived class takes precedence over the property or method in the parent class. Referencing the property returns the value of the property on the child, while referencing the method calls the method on the child.

Use the parent::method() notation to access an overridden method on an object’s parent class:

parent::birthday(); // call parent class's birthday() method

A common mistake is to hardcode the name of the parent class into calls to overridden methods:

Creature::birthday(); // when Creature is the parent class

This is a mistake because it distributes knowledge of the parent class’s name throughout the derived class. Using parent:: centralizes the knowledge of the parent class in the extends clause.

If a method might be subclassed and you want to ensure that you’re calling it on the current class, use the self::method() notation:

self::birthday(); // call this class's birthday() method

To check if an object is an instance of a particular class or if it implements a particular interface (see the section “Interfaces”), you can use the instanceof operator:

if ($object instanceof Animal) {
 // do something
}

Traits

Traits provide a mechanism for reusing code outside of a class hierarchy. Traits allow you to share functionality across different classes that don’t (and shouldn’t) share a common ancestor in a class hierarchy. Here’s the syntax for a trait definition:

trait traitname [ extends baseclass ] {
 [ use traitname, [ traitname, ... ]; ]

 [ visibility $property [ = value ]; ... ]

 [ function functionname (args) {
 // code
 }
 ...
 ]
}

To declare that a class should include a trait’s methods, include the use keyword and any number of traits, separated by commas:

trait Logger {
 public function log($logString) {
 $className = __CLASS__;
 echo date("Y-m-d h:i:s", time()) . ": [{$className}] {$logString}";
 }
}

class User {
 use Logger;

 public $name;

 function __construct($name = '') {
 $this->name = $name;
 $this->log("Created user '{$this->name}'");
 }

 function __toString() {
 return $this->name;
 }
}

class UserGroup {
 use Logger;

 public $users = array();

 public function addUser(User $user) {
 if (!in_array($this->users, $user)) {
 $this->users[] = $user;
 $this->log("Added user '{$user}' to group");
 }
 }
}

$group = new UserGroup;
$group->addUser(new User("Franklin"));
2012-03-09 07:12:58: [User] Created user 'Franklin'2012-03-09 07:12:58:
[UserGroup] Added user 'Franklin' to group

The methods defined by the Logger trait are available to instances of the UserGroup class as if they were defined in that class.

To declare that a trait should be composed of other traits, include the use statement in the trait’s declaration, followed by one or more trait names separated by commas, as shown here:

trait First {
 public function doFirst( {
 echo "first\n";
 }
}

trait Second {
 public function doSecond() {
 echo "second\n";
 }
}

trait Third {
 use First, Second;

 public function doAll() {
 $this->doFirst();
 $this->doSecond();
 }
}

class Combined {
 use Third;
}

$object = new Combined;
$object->doAll();
firstsecond

Traits can declare abstract methods.

If a class uses multiple traits defining the same method, PHP gives a fatal error. However, you can override this behavior by telling the compiler specifically which implementation of a given method you want to use. When defining which traits a class includes, use the insteadof keyword for each conflict:

trait Command {
 function run() {
 echo "Executing a command\n";
 }
}

trait Marathon {
 function run() {
 echo "Running a marathon\n";
 }
}

class Person {
 use Command, Marathon {
 Marathon::run insteadof Command;
 }
}

$person = new Person;
$person->run();
Running a marathon

Instead of picking just one method to include, you can use the as keyword to alias a trait’s method within the class including it to a different name. You must still explicitly resolve any conflicts in the included traits. For example:

trait Command {
 function run() {
 echo "Executing a command";
 }
}

trait Marathon {
 function run() {
 echo "Running a marathon";
 }
}

class Person {
 use Command, Marathon {
 Command::run as runCommand;
 Marathon::run insteadof Command;
 }
}

$person = new Person;
$person->run();
$person->runCommand();
Running a marathonExecuting a command

Introspection

Introspection is the ability of a program to examine an object’s characteristics, such as its name, parent class (if any), properties, and methods. With introspection, you can write code that operates on any class or object. You don’t need to know which methods or properties are defined when you write your code; instead, you can discover that information at runtime, which makes it possible for you to write generic debuggers, serializers, profilers, and the like. In this section, we look at the introspective functions provided by PHP.

Examining Classes

To determine whether a class exists, use the class_exists() function, which takes in a string and returns a Boolean value. Alternately, you can use the get_declared_classes() function, which returns an array of defined classes and checks if the class name is in the returned array:

$doesClassExist = class_exists(classname);

$classes = get_declared_classes();
$doesClassExist = in_array(classname, $classes);

You can get the methods and properties that exist in a class (including those that are inherited from superclasses) using the get_class_methods() and get_class_vars() functions. These functions take a class name and return an array:

$methods = get_class_methods(classname);
$properties = get_class_vars(classname);

The class name can be either a variable containing the class name, a bare word, or a quoted string:

$class = "Person";
$methods = get_class_methods($class);
$methods = get_class_methods(Person); // same
$methods = get_class_methods("Person"); // same

The array returned by get_class_methods() is a simple list of method names. The associative array returned by get_class_vars() maps property names to values and also includes inherited properties.

One quirk of get_class_vars() is that it returns only properties that have default values and are visible in the current scope; there’s no way to discover uninitialized properties.

Use get_parent_class() to find a class’s parent class:

$superclass = get_parent_class(classname);

Example 6-1 lists the displayClasses() function, which displays all currently declared classes and the methods and properties for each.

Example 6-1. Displaying all declared classes
function displayClasses() {
 $classes = get_declared_classes();

 foreach ($classes as $class) {
 echo "Showing information about {$class}<br />";
 $reflection = new ReflectionClass($class);

 $isAnonymous = $reflection->isAnonymous() ? "yes" : "no";
 echo "Is Anonymous: {$isAnonymous}<br />";

 echo "Class methods:<br />";
 $methods = $reflection->getMethods(ReflectionMethod::IS_STATIC);

 if (!count($methods)) {
 echo "<i>None</i><br />";
 }
 else {
 foreach ($methods as $method) {
 echo "<b>{$method}</b>()<br />";
 }
 }

 echo "Class properties:<br />";

 $properties = $reflection->getProperties();

 if (!count($properties)) {
 echo "<i>None</i><br />";
 }
 else {
 foreach(array_keys($properties) as $property) {
 echo "<b>\${$property}</b><br />";
 }
 }

 echo "<hr />";
 }
}

Sample Introspection Program

Example 6-2 shows a collection of functions that display a reference page of information about an object’s properties, methods, and inheritance tree.

Example 6-2. Object introspection functions
// return an array of callable methods (include inherited methods)
function getCallableMethods($object): Array {
 $reflection = new ReflectionClass($object);
 $methods = $reflection->getMethods();

 return $methods;
}

// return an array of superclasses
function getLineage($object): Array {
 $reflection = new ReflectionClass($object);

 if ($reflection->getParentClass()) {
 $parent = $reflection->getParentClass();

 $lineage = getLineage($parent);
 $lineage[] = $reflection->getName();
 }
 else {
 $lineage = array($reflection->getName());
 }

 return $lineage;
}

// return an array of subclasses
function getChildClasses($object): Array {
 $reflection = new ReflectionClass($object);

 $classes = get_declared_classes();

 $children = array();

 foreach ($classes as $class) {
 $checkedReflection = new ReflectionClass($class);

 if ($checkedReflection->isSubclassOf($reflection->getName())) {
 $children[] = $checkedReflection->getName();
 }
 }

 return $children;
}

// return an array of properties
function getProperties($object): Array {
 $reflection = new ReflectionClass($object);

 return $reflection->getProperties();
}

// display information on an object
function printObjectInfo($object) {
 $reflection = new ReflectionClass($object);
 echo "<h2>Class</h2>";
 echo "<p>{$reflection->getName()}</p>";

 echo "<h2>Inheritance</h2>";

 echo "<h3>Parents</h3>";
 $lineage = getLineage($object);
 array_pop($lineage);

 if (count($lineage) > 0) {
 echo "<p>" . join(" -&gt; ", $lineage) . "</p>";
 }
 else {
 echo "<i>None</i>";
 }

 echo "<h3>Children</h3>";
 $children = getChildClasses($object);
 echo "<p>";

 if (count($children) > 0) {
 echo join(', ', $children);
 }
 else {
 echo "<i>None</i>";
 }

 echo "</p>";

 echo "<h2>Methods</h2>";
 $methods = getCallableMethods($object);

 if (!count($methods)) {
 echo "<i>None</i><br />";
 }
 else {
 foreach($methods as $method) {
 echo "<b>{$method}</b>();<br />";
 }
 }

 echo "<h2>Properties</h2>";
 $properties = getProperties($object);

 if (!count($properties)) {
 echo "<i>None</i><br />";
 }
 else {
 foreach(array_keys($properties) as $property) {
 echo "<b>\${$property}</b> = " . $object->$property . "<br />";
 }
 }

 echo "<hr />";
}

Here are some sample classes and objects that exercise the introspection functions from Example 6-2:

class A {
 public $foo = "foo";
 public $bar = "bar";
 public $baz = 17.0;

 function firstFunction() { }

 function secondFunction() { }
}

class B extends A {
 public $quux = false;

 function thirdFunction() { }
}

class C extends B { }

$a = new A();
$a->foo = "sylvie";
$a->bar = 23;

$b = new B();
$b->foo = "bruno";
$b->quux = true;

$c = new C();

printObjectInfo($a);
printObjectInfo($b);
printObjectInfo($c);

Serialization

Serializing an object means converting it to a bytestream representation that can be stored in a file. This is useful for persistent data; for example, PHP sessions automatically save and restore objects. Serialization in PHP is mostly automatic—it requires little extra work from you, beyond calling the serialize() and unserialize() functions:

$encoded = serialize(something);
$something = unserialize(encoded);

Serialization is most commonly used with PHP’s sessions, which handle the serialization for you. All you need to do is tell PHP which variables to keep track of, and they’re automatically preserved between visits to pages on your site. However, sessions are not the only use of serialization—if you want to implement your own form of persistent objects, serialize() and unserialize() are a natural choice.

An object’s class must be defined before unserialization can occur. Attempting to unserialize an object whose class is not yet defined puts the object into stdClass, which renders it almost useless. One practical consequence of this is that if you use PHP sessions to automatically serialize and unserialize objects, you must include the file containing the object’s class definition in every page on your site. For example, your pages might start like this:

include "object_definitions.php"; // load object definitions
session_start(); // load persistent variables
?>
<html>...

PHP has two hooks for objects during the serialization and unserialization process: __sleep() and __wakeup(). These methods are used to notify objects that they’re being serialized or unserialized. Objects can be serialized if they do not have these methods; however, they won’t be notified about the process.

The __sleep() method is called on an object just before serialization; it can perform any cleanup necessary to preserve the object’s state, such as closing database connections, writing out unsaved persistent data, and so on. It should return an array containing the names of the data members that need to be written into the bytestream. If you return an empty array, no data is written.

Conversely, the __wakeup() method is called on an object immediately after an object is created from a bytestream. The method can take any action it requires, such as reopening database connections and other initialization tasks.

Example 6-3 is an object class, Log, that provides two useful methods: write() to append a message to the logfile, and read() to fetch the current contents of the logfile. It uses __wakeup() to reopen the logfile and __sleep() to close the logfile.

Example 6-3. The Log.php file
class Log {
 private $filename;
 private $fh;

 function __construct($filename) {
 $this->filename = $filename;
 $this->open();
 }

 function open() {
 $this->fh = fopen($this->filename, 'a') or die("Can't open {$this->filename}");
 }

 function write($note) {
 fwrite($this->fh, "{$note}\n");
 }

 function read() {
 return join('', file($this->filename));
 }

 function __wakeup(array $data): void {
 $this->filename = $data["filename"];
 $this->open();
 }

 function __sleep() {
 // write information to the account file
 fclose($this->fh);

 return ["filename" => $this->filename];
 }
}

Store the Log class definition in a file called Log.php. The HTML front page in Example 6-4 uses the Log class and PHP sessions to create a persistent log variable, $logger.

Example 6-4. front.php
<?php
include_once "Log.php";
session_start();
?>

<html><head><title>Front Page</title></head>
<body>

<?php
$now = strftime("%c");

if (!isset($_SESSION['logger'])) {
 $logger = new Log("/tmp/persistent_log");
 $_SESSION['logger'] = $logger;
 $logger->write("Created $now");

 echo("<p>Created session and persistent log object.</p>");
}
else {
 $logger = $_SESSION['logger'];
}

$logger->write("Viewed first page {$now}");

echo "<p>The log contains:</p>";
echo nl2br($logger->read());
?>

<a href="next.php">Move to the next page</a>

</body></html>

Example 6-5 shows the file next.php, an HTML page. Following the link from the front page to this page triggers the loading of the persistent object $logger. The __wakeup() call reopens the logfile so the object is ready to be used.

Example 6-5. next.php
<?php
include_once "Log.php";
session_start();
?>

<html><head><title>Next Page</title></head>
<body>

<?php
$now = strftime("%c");
$logger = $_SESSION['logger'];
$logger->write("Viewed page 2 at {$now}");

echo "<p>The log contains:";
echo nl2br($logger->read());
echo "</p>";
?>

</body></html>

What’s Next

Learning how to use objects in your own scripts is an enormous task. In the next chapter, we transition from language semantics to practice and show you one of PHP’s most commonly used set of object-oriented classes—the date and time classes.