Advanced OOP - PHP Advanced and Object-Oriented Programming (2013) - Visual Quickpro Guide

PHP Advanced and Object-Oriented Programming (2013)

Visual Quickpro Guide

5. Advanced OOP


In This Chapter

Advanced Theories

Inheriting Classes

Inheriting Constructors and Destructors

Overriding Methods

Access Control

Using the Scope Resolution Operator

Creating Static Members

Review and Pursue


Chapter 4, “Basic Object-Oriented Programming,” covers the fundamental concepts of OOP in PHP. A fair amount of theory is also discussed there, because that’s half the OOP battle. Here, things get more advanced (hence the chapter title!), going into the more abstract aspects of OOP, with ample time again given to theory.

Most of the topics discussed in this chapter involve inheritance, the understanding of which is crucial to using OOP. You’ll learn how to have one class inherit from another, and the various ways that a class’s properties and methods can be inherited. Along the way, you’ll begin creating more practical examples, and encounter more theory, particularly in the sidebars.

Advanced Theories

This chapter begins with a brief discussion of a few key concepts in advanced OOP. Chapter 4 introduced most of the basic terms: class, object, modularity, and abstraction. A few more were also referenced: inheritance, overriding, encapsulation, and visibility. Of these latter four notions, all of which arise in this chapter, inheritance is far and away the most important in advanced object-oriented programming.

Object inheritance is where one class is derived from another, just as humans inherit qualities from their parents. Of course, the “qualities” in the object-oriented world are attributes (variables) and methods (functions). Through inheritance, you can define one class that is “born” with the same attributes and methods as another image. The inherited child class can even have its own unique qualities that the parent doesn’t have image.

image

image A child class inherits (i.e., has) all of the attributes and methods of its parent class (there can be exceptions to this, but assume this to be true for now).

image

image Child classes can add their own members to the ones they inherited. In this way a child can separate itself (functionally speaking) from its parent.


Indicating Inheritance in UML

In a UML class diagram, inheritance is represented using an arrow between the two classes, as in these early figures. The arrow should go from the subclass to the base class (i.e., the arrow points to the parent). Conventionally, the parent class is placed above the child classes.

Finally, UML would not have you repeat the inherited attributes and properties in the child classes, as in these early figures (that is unnecessarily redundant). I’ve only done so to better illustrate the point that a child class often inherits the members in its parent.



Inheritance Terminology

With class definitions, the main terms are attributes and methods, meaning variables and functions, respectively. The combination of attributes and methods make up the members of a class.

With inheritance you have a parent class and a child class: the latter is inherited from the former. You’ll also see these described as a base class or superclass and its derived class or subclass.


But inheritance isn’t necessarily a simple one-to-one relationship. There’s no limit to how many times inheritance can occur: multiple classes can inherit from the same parent image or a class can be a child of a child image. This speaks to the powerful reusability of class code.

image

image A single parent class can have unlimited offspring, each customized in its own way.

image

image Inheritance can theoretically have limitless depth, with each child inheriting all the members of its parent (again, not always so, but...).

Once you’ve defined a class that inherits from another, it doesn’t take long to start thinking how nice it’d be if it behaved just slightly differently. You can add new attributes and methods, but what if you wanted to change the behavior of the parent class’s methods? It would be wrong to change the definition of the parent class (presumably it works as it should, and besides, other classes might inherit from it too image). Instead, you can override a parent class’s method to customize it for the new class. This is polymorphism, where calling the same method can have different results, depending on the object type. This probably doesn’t mean much yet, but you’ll understand in time.

Acknowledging (as I just did) that it’s not a good thing for one class to muck around in another, the concept of visibility exists. Visibility controls what members of a class can be accessed or altered by other classes (or even outside of any class).

As you can tell already, once you introduce inheritance, the OOP world expands exponentially. Just as in the previous chapter, I’ll attempt to go through this sea of information slowly, to make sure that it really settles in. Some of the examples will be designed for illumination of a concept rather than real-world implementations, but you’ll still see plenty of practical examples.


Tip

For many of the images in this chapter, I’ll use a more minimal version of UML (introduced at the end of Chapter 4). Feel free to flesh out the UML on your own, for practice.


Inheriting Classes

One of the ways in which objects make programming faster is the ability to use one class definition as the basis for another. This process is referred to as inheritance.

Going back to the User example mentioned in Chapter 4, if the User class has the attributes username, userId, email, and password and it has the methods login and logout, you could create another class called Admin that is an extension of User. Along with the aforementioned variables and functions, an Admin object might also have the attribute of accessLevel and the method of editUser image.

image

image The Admin class can have all the same members as User, while adding its own.

This kind of inheritance means the two classes have an “is a” relationship, in that an Admin is a type of User. When you’re in design situations where one thing is just a more specific type of another thing, you’re probably going to want to use inheritance.

To make a child class from a parent, use the extends keyword. Assuming you have already defined the ClassName class, you can create a child like so:

class ChildClass extends ClassName { }

As written, the class ChildClass will possess all the members of its parent, ClassName. Now you can modify this class to adapt it to your specific needs without altering the original class. Ideally, once you’ve created a solid parent class, you will never need to modify it again and can use child classes to tailor the code to your individual requirements.


The instanceof Keyword

The instanceof keyword can be used to see if a particular object is of a certain class type:

if ($obj instanceof SomeClass) { ...

Notice that you don’t put the class’s name in quotation marks. Also—and this is important—in order for this to work, the PHP script must have access to the SomeClass definition.


Script 5.1. This example script shows how two classes can be derived from the same parent. Each can access all the members of the parent, and each has defined its own custom method.


1 <!doctype html>
2 <html lang="en">
3 <head>
4 <meta charset="utf-8">
5 <title>Pets</title>
6 <link rel="stylesheet" href="style.css">
7 </head>
8 <body>
9 <?php # Script 5.1 - pets1.php
10 // This page defines and uses the Pet, Cat, and Dog classes.
11
12 # ***** CLASSES ***** #
13
14 /* Class Pet.
15 * The class contains one attribute: name.
16 * The class contains three methods:
17 * - _ _construct()
18 * - eat()
19 * - sleep()
20 */
21 class Pet {
22
23 // Declare the attributes:
24 public $name;
25
26 // Constructor assigns the pet's name:
27 function _ _construct($pet_name) {
28 $this->name = $pet_name;
29 }
30
31 // Pets can eat:
32 function eat() {
33 echo "<p>$this->name is eating.</p>";
34 }
35
36 // Pets can sleep:
37 function sleep() {
38 echo "<p>$this->name is sleeping.</p>";
39 }
40
41 } // End of Pet class.
42
43 /* Cat class extends Pet.
44 * Cat has additional method: climb().
45 */
46 class Cat extends Pet {
47 function climb() {
48 echo "<p>$this->name is climbing.</p>";
49 }
50 } // End of Cat class.
51
52 /* Dog class extends Pet.
53 * Dog has additional method: fetch().
54 */
55 class Dog extends Pet {
56 function fetch() {
57 echo "<p>$this->name is fetching.</p>";
58 }
59 } // End of Dog class.
60
61 # ***** END OF CLASSES ***** #
62
63 // Create a dog:
64 $dog = new Dog('Satchel');
65
66 // Create a cat:
67 $cat = new Cat('Bucky');
68
69 // Feed them:
70 $dog->eat();
71 $cat->eat();
72
73 // Nap time:
74 $dog->sleep();
75 $cat->sleep();
76
77 // Do animal-specific thing:
78 $dog->fetch();
79 $cat->climb();
80
81 // Delete the objects:
82 unset($dog, $cat);
83
84 ?>
85 </body>
86 </html>


For an example implementation of this, I’ll start with a silly (but comprehensible) pets example. Say you have two pets: a cat and a dog. Both animals have a name, and they both eat and sleep. Cats differ from dogs in that they can climb trees and dogs differ from cats in that they can fetch. Being able to describe these qualities and relationships in plain language leads to the inheritance structure you would create image.

image

image How the pet-cat-dog relationship would be implemented as objects.

To inherit from a class

1. Begin a new PHP script in your text editor or IDE, to be named pets1.php, starting with the HTML (Script 5.1):

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Pets</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<?php # Script 5.1 - pets1.php

To make things easier, I’m going to put all the class definitions and the use of these classes in this same script. In a real application, you would separate your class files from the program files that use them.

2. Start declaring the Pet class:

class Pet {
public $name;

Pet has one attribute: the pet’s name.

3. Create the constructor:

function _ _construct($pet_name) {
$this->name = $pet_name;
}

The constructor takes one argument: the name of the pet. This gets assigned to the class’s $name attribute.

4. Define the eat() method:

function eat() {
echo "<p>$this->name is eating.</p>";
}

This method simply reports the name of the animal eating.

5. Define the sleep() method and complete the class:

function sleep() {
echo "<p>$this->name is sleeping.</p>";
}
} // End of Pet class.

6. Declare the Cat class:

class Cat extends Pet {
function climb() {
echo "<p>$this->name is climbing.</p>";
}
} // End of Cat class.

The Cat class extends Pet, meaning that it has all the attributes and methods of Pet. Added to those is one new method, climb(). The method can refer to the $name attribute via $this->name because the attribute is also part of this class (thanks to inheritance).

7. Declare the Dog class:

class Dog extends Pet {
function fetch() {
echo "<p>$this->name is fetching.</p>";
}
} // End of Dog class.

8. Create two new pets:

$dog = new Dog('Satchel');
$cat = new Cat('Bucky');

Note that you’re creating objects of the child class types, not of Pet.

9. Make the pets do the things they do:

$dog->eat();
$cat->eat();
$dog->sleep();
$cat->sleep();
$dog->fetch();
$cat->climb();

Each subclass object can invoke the methods defined in the parent class as well as its own new methods: fetch() and climb(). Note that $dog could not invoke the climb() method, nor could $cat call fetch().

10. Complete the page:

unset($dog, $cat);
?>
</body>
</html>

You don’t have to unset the objects, but it makes for tidier code.

11. Save the file as pets1.php, place it in your Web directory, and test in your Web browser image.

image

image Two objects are created from different derived classes. Then the various methods are called. Understanding this result and the code in pets1.php is key to the rest of the chapter’s material.


Tip

In this example, you could create an object of type Pet. That object would have a name and could eat() and sleep(), but it could not fetch() or climb().


Inheriting Constructors and Destructors

The pets example shows how you can create one class (i.e., Pet) and then derive other classes from it (Dog and Cat). These other classes can have their own methods, unique to themselves, such as climb() and fetch().

There are two methods that are common to many classes: constructors and destructors (see Chapter 4 for a detailed description). The Pet class has a constructor but no need for a destructor. What would happen, then, if Cat or Dog also had a constructor? By definition, this method is always called _ _construct(). How does PHP determine which version of the constructor to execute?

As a rule, PHP will always call the constructor for the class just instantiated image. The same rule applies for destructors. Further, unlike in some other OOP languages, in PHP, when you create an object of a child class, the parent class’s constructor is not automatically called.

image

image When an object is created, PHP will always call the constructor of that object’s class type.

This next, somewhat more practical, example will extend the Rectangle class (Script 4.5, defined in Chapter 4) to create a Square class (because all squares are rectangles but not all rectangles are squares).

To create subclass constructors

1. Begin a new PHP script in your text editor or IDE, to be named square.php, starting with the HTML (Script 5.2):

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Square</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<?php # Script 5.2 - square.php

Script 5.2. The Square class is derived from Rectangle but has its own constructor. That constructor, not Rectangle’s, will be called when an object of type Square is created.


1 <!doctype html>
2 <html lang="en">
3 <head>
4 <meta charset="utf-8">
5 <title>Square</title>
6 <link rel="stylesheet" href="style.css">
7 </head>
8 <body>
9 <?php # Script 5.2 - square.php
10 // This page declares and uses the Square class which is derived from Rectangle (Script 4.5).
11
12 // Include the first class definition:
13 require('Rectangle.php');
14
15 // Create the Square class.
16 // The class only adds its own constructor.
17 class Square extends Rectangle {
18
19 // Constructor takes one argument.
20 // This value is assigned to the
21 // Rectangle width and height attributes.
22 function _ _construct($side = 0) {
23 $this->width = $side;
24 $this->height = $side;
25 }
26
27 } // End of Square class.
28
29 // Rectangle dimensions:
30 $width = 21;
31 $height = 98;
32
33 // Print a little introduction:
34 echo "<h2>With a width of $width and a height of $height...</h2>";
35
36 // Create a new rectangle:
37 $r = new Rectangle($width, $height);
38
39 // Print the area.
40 echo '<p>The area of the rectangle is ' . $r->getArea() . '</p>';
41
42 // Print the perimeter.
43 echo '<p>The perimeter of the rectangle is ' . $r->getPerimeter() . '</p>';
44
45 // Square dimensions:
46 $side = 60;
47
48 // Print a little introduction:
49 echo "<h2>With each side being $side... </h2>";
50
51 // Create a new object:
52 $s = new Square($side);
53
54 // Print the area.
55 echo '<p>The area of the square is ' . $s->getArea() . '</p>';
56
57 // Print the perimeter.
58 echo '<p>The perimeter of the square is ' . $s->getPerimeter() . '</p>';
59
60 // Delete the objects:
61 unset($r, $s);
62
63 ?>
64 </body>
65 </html>


2. Include the Rectangle class:

require('Rectangle.php');

You’ll need to make sure that the Rectangle.php file (Script 4.5) is in the same directory as this script.

3. Declare the Square class:

class Square extends Rectangle {
function _ _construct($side = 0) {
$this->width = $side;
$this->height = $side;
}
} // End of Square class.

The premise is simple: there’s no reason to have to pass both a height and a width value to a child of the Rectangle class when you know you’re creating a square. So a new constructor is defined that only takes one argument. That value will be assigned, within the constructor, to the parent class’s attributes.

Note that in order for this class extension to work, it must be able to access the Rectangle definition (so that file must be included prior to this point).

4. Create a rectangle and report on it:

$width = 21;
$height = 98;
echo "<h2>With a width of $width and a height of $height...</h2>";
$r = new Rectangle($width, $height);
echo '<p>The area of the rectangle is ' . $r->getArea() . '</p>';
echo '<p>The perimeter of the rectangle is ' . $r->getPerimeter() . '</p>';

This code is also from Chapter 4. It just creates a rectangle and prints its area and perimeter.

5. Repeat Step 4 for a square:

$side = 60;
echo "<h2>With each side being $side...</h2>";
$s = new Square($side);
echo '<p>The area of the square is ' . $s->getArea() . '</p>';
echo '<p>The perimeter of the square is ' . $s->getPerimeter() . '</p>';

This code differs from that in Step 4 in that only one value needs to be passed to the Square constructor. Then all the other methods can be called just the same.

6. Complete the page:

unset($r, $s);
?>
</body>
</html>

7. Save the file as square.php, place it in your Web directory, and test in your Web browser image.

image

image Even though the Square constructor takes only one argument (Script 5.2), the use of the Rectangle methods, and the end result, works just the same.


Tip

As stated in Chapter 4, by definition, constructors never return anything.



Tip

A general OOP recommendation is that all classes have a constructor, inherited or otherwise.



Tip

You can call the parent class’s constructor, if need be, using the scope resolution operator, discussed later in this chapter.



Simple Inheritance Design

Any time one class inherits from another, the result should be a more specific description of a thing. Hence, I go from Pet to Dog or Cat and from Rectangle to Square. When deciding where to place methods, including constructors and destructors, you have to think about whether that functionality is universal or specific.

In the Pet example, the constructor sets the pet’s name, which is universal for all pets. So the Dog and Cat classes don’t need their own constructors. In the Rectangle example, its constructor sets the height and width. But a square doesn’t have two different dimensions; having a new constructor for it is valid.


Overriding Methods

So far I’ve covered how one class can inherit from another class and how the child classes can have their own new methods. The previous example demonstrated that subclasses can even define their own constructors (and destructors, implicitly), which will be used in lieu of the parent class’s constructors and destructors. This same thinking—creating alternative method definitions in subclasses—can be applied to the other class methods, too. This concept is called overriding a method.

To override a method in PHP, the subclass must define a method with the exact same name and number of arguments as the parent class image:

image

image When related classes have overridden methods, which method is called depends on the type of the object calling it. Note that for $obj2, the code of the overridden scream() method in SomeOtherClass is used in lieu of the original scream() (hence the different scream in the last three lines).

class SomeClass {
function scream($count = 1) {
for ($i = 0; $i < $count; $i++) {
echo 'Eek!<br>';
}
}
}
class SomeOtherClass extends SomeClass{
function scream($count = 1) {
for ($i = 0; $i < $count; $i++) {
echo 'Whoohoo!<br>';
}
}
}
$obj1 = new SomeClass();
$obj1->scream();
$obj1->scream(2);
$obj2 = new SomeOtherClass();
$obj2->scream();
$obj2->scream(2);

Overriding methods is a common and useful feature of advanced object-oriented programming. As mentioned in the first section of this chapter, overriding methods creates polymorphism, where calling the same method can have different results, depending on the object type.

As a simple example of this, I’ll return to the Pet, Dog, and Cat classes. Instead of having separate climb() and fetch() methods, you’ll implement that functionality as an overridden play() method.

To override methods

1. Open pets1.php (Script 5.1) in your text editor or IDE.

2. Add a play() method to the Pet class (Script 5.3):

function play() {
echo "<p>$this->name is playing.</p>";
}

This is the method that will be overridden. It just prints the name of the pet that is playing.

Script 5.3. The Cat and Dog classes override the Pet play() method, giving it new functionality. Which version of play() gets called depends on the type of the object calling it.


1 <!doctype html>
2 <html lang="en">
3 <head>
4 <meta charset="utf-8">
5 <title>Pets</title>
6 <link rel="stylesheet" href="style.css">
7 </head>
8 <body>
9 <?php # Script 5.3 - pets2.php
10 // This page defines and uses the Pet, Cat, and Dog classes.
11
12 # ***** CLASSES ***** #
13
14 /* Class Pet.
15 * The class contains one attribute: name.
16 * The class contains four methods:
17 * - _ _construct()
18 * - eat()
19 * - sleep()
20 * - play()
21 */
22 class Pet {
23 public $name;
24 function _ _construct($pet_name) {
25 $this->name = $pet_name;
26 }
27 function eat() {
28 echo "<p>$this->name is eating.</p>";
29 }
30 function sleep() {
31 echo "<p>$this->name is sleeping.</p>";
32 }
33
34 // Pets can play:
35 function play() {
36 echo "<p>$this->name is playing.</p>";
37 }
38
39 } // End of Pet class.
40
41 /* Cat class extends Pet.
42 * Cat overrides play().
43 */
44 class Cat extends Pet {
45 function play() {
46 echo "<p>$this->name is climbing.</p>";
47 }
48 } // End of Cat class.
49
50 /* Dog class extends Pet.
51 * Dog overrides play().
52 */
53 class Dog extends Pet {
54 function play() {
55 echo "<p>$this->name is fetching.</p>";
56 }
57 } // End of Dog class.
58
59 # ***** END OF CLASSES ***** #
60
61 // Create a dog:
62 $dog = new Dog('Satchel');
63
64 // Create a cat:
65 $cat = new Cat('Bucky');
66
67 // Create an unknown type of pet:
68 $pet = new Pet('Rob');
69
70 // Feed them:
71 $dog->eat();
72 $cat->eat();
73 $pet->eat();
74
75 // Nap time:
76 $dog->sleep();
77 $cat->sleep();
78 $pet->sleep();
79
80 // Have them play:
81 $dog->play();
82 $cat->play();
83 $pet->play();
84
85 // Delete the objects:
86 unset($dog, $cat, $pet);
87
88 ?>
89 </body>
90 </html>


3. In the Cat class, change the name of climb() to play().

Now the Pet class’s play() method has been overridden in the Cat class.

4. In the Dog class, change the name of fetch() to play().

5. After the class declarations, create an object of type Pet:

$pet = new Pet('Rob');

To see the impact of overriding a method, you’ll create an object of the parent class as well.

6. Add activities for the Pet object:

$pet->eat();
$pet->sleep();

7. Make all three objects play:

$dog->play();
$cat->play();
$pet->play();

These three lines will reveal which class’s method gets called by which object.


Final Methods

Most methods in classes can be overridden. The exception is if a function is defined as final:

final function myFunc() {...}

A final method’s definition cannot be altered by any subclass.

A class can also be declared final, meaning that it cannot be extended.


8. Delete the calls to $dog->fetch() and $cat->climb().

9. Also unset the $pet object toward the end of the script:

unset($dog, $cat, $pet);

10. Save the file as pets2.php, place it in your Web directory, and test in your Web browser image.

image

image Here, cat-specific and dog-specific play() methods are introduced, overriding the parent class’s play() method. A third object, of type Pet, was also added.


Tip

The combination of a function’s name and its arguments (the number of arguments, specifically) is referred to as the function’s signature. In PHP 5, except for constructors, any derived class must use the same signature when overriding a method.



Tip

The Square class could logically override the Rectangle isSquare() method. It would be defined as simply

function isSquare() {
return true;
}



Tip

Overriding a method in such a way that it also takes a different number of arguments than the original is referred to as overloading a method. This can be accomplished in PHP but not as easily as overriding one.



Tip

The method defined in a parent class can be called the default behavior, because this is what that method will do, even in instances of subclasses, unless the subclasses specifically override that behavior.


Access Control

Access control, which is also called visibility, dictates how accessible a class’s properties and methods are... in other words: where the class’s members can be referenced and where they are inaccessible.

There are three levels of visibility: public, protected, and private. To establish the visibility of an attribute, prefix the variable’s declaration with one of these keywords:

class ClassName {
public $var1 = 'Hello';
private $var2 = 'world';
protected $var3 = 42;
}

You’ve already been doing this, because indicating an attribute’s visibility is required in PHP. Thus far, every attribute has been declared public.

To establish the visibility of a method, prefix the function’s declaration with one of the keywords:

class ClassName {
public function myFunction() {
// Function code.
}
}

Methods lacking the accessibility declaration are considered to be public. And because methods often are public, the visibility for them is frequently omitted.

Think of each term as prescribing a more limited circle in which the member can be accessed image. A public member is the most accessible: available to methods within the class itself, in inherited classes, and outside of any class. For example, because the $name attribute in Pet is public, you can do this:

image

image The more restricted the visibility, the smaller the realm where the attribute or method is accessible.

$pet = new Pet('Charlie');
$pet->name = 'Fungo';

Again, because $name is public, it’s also available within the Dog and Cat classes, as you’ve already seen.

Protected members can only be accessed within the class and derived subclasses. This means that should the $name attribute in Pet be made protected, you could still use it within methods found in the Dog or Cat classes, but not directly through an object instance of any of those classes.

Private is the most restrictive; those members are only accessible within the class that defines them. Private class members cannot be accessed by subclasses or through an object instance of that class. If the $name attribute in Pet was made private, you could only reference it within the Pet class definition (i.e., within one of its methods).

It may seem odd for a class to have attributes, or members, that are inaccessible, but this is a valuable, important OOP concept, called encapsulation. Simply put, encapsulation is the hiding of information (actual data or processes) that does not need to be available outside of the class. From a design perspective, a good class is a usable entity without your necessarily knowing how it works internally. Further, there are many times where some of the internal data shouldn’t be accessible externally. For example, a database class needs an internal database connection, but there’s no reason why that connection should be accessible outside of the class (in fact, it shouldn’t be).

Looking at the Pet class, its name attribute should be made protected. It makes sense for the class and its subclasses to be able to access the name, but you shouldn’t be able to change the name outside of the class. The same applies to the $width and $height attributes in Rectangle: they should be protected.

As with most concepts in OOP, there are two things you should learn: how visibility works and how you use it. To make clear how access control works, I’ll run through a dummy example that just plays around with the accessibility of attributes. After this example, the topic of visibility will become clearer, as you’ll use it in other examples.

To control member access

1. Begin a new PHP script in your text editor or IDE, to be named visibility.php, starting with the HTML (Script 5.4):

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Visibility</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<?php # Script 5.4 - visibility.php


Indicating Visibility in UML

In a UML class diagram, the accessibility of a class member can be indicated by prefacing the member with

• +, for public

• -, for private

• #, for protected

Thus, the Pet class as defined to this point would have:

+name:string
+_ _construct($pet_name:string):void


Script 5.4. This script demonstrates access control by showing what can and cannot be done with attributes of different visibility.


1 <!doctype html>
2 <html lang="en">
3 <head>
4 <meta charset="utf-8">
5 <title>Visibility</title>
6 <link rel="stylesheet" href="style.css">
7 </head>
8 <body>
9 <?php # Script 5.4 - visibility.php
10 // This page defines and uses the Test and LittleTest classes.
11
12 # ***** CLASSES ***** #
13
14 /* Class Test.
15 * The class contains three attributes:
16 * - public $public
17 * - protected $protected
18 * - private $_private
19 * The class defines one method: printVar().
20 */
21 class Test {
22
23 // Declare the attributes:
24 public $public = 'public';
25 protected $protected = 'protected';
26 private $_private = 'private';
27
28 // Function for printing a variable's value:
29 function printVar($var) {
30 echo "<p>In Test, \$$var: '{$this->$var}'.</p>";
31 }
32
33 } // End of Test class.
34
35 /* LittleTest class extends Test.
36 * LittleTest overrides printVar().
37 */
38 class LittleTest extends Test {
39 function printVar($var) {
40 echo "<p>In LittleTest, \$$var: '{$this->$var}'.</p>";
41 }
42 } // End of LittleTest class.
43
44 # ***** END OF CLASSES ***** #
45
46 // Create the objects:
47 $parent = new Test();
48 $child = new LittleTest();
49
50 // Print the current value of $public:
51 echo '<h1>Public</h1>';
52 echo '<h2>Initially...</h2>';
53 $parent->printVar('public');
54 $child->printVar('public');
55 // Also echo $parent->public or echo $child->public.
56
57 // Modify $public and reprint:
58 echo '<h2>Modifying $parent->public...</h2>';
59 $parent->public = 'modified';
60 $parent->printVar('public');
61 $child->printVar('public');
62
63 // Print the current value of $protected:
64 echo '<hr><h1>Protected</h1>';
65 echo '<h2>Initially...</h2>';
66 $parent->printVar('protected');
67 $child->printVar('protected');
68
69 // Attempt to modify $protected and reprint:
70 echo '<h2>Attempting to modify $parent->protected...</h2>';
71 $parent->protected = 'modified';
72 $parent->printVar('protected');
73 $child->printVar('protected');
74
75 // Print the current value of $_private:
76 echo '<hr><h1>Private</h1>';
77 echo '<h2>Initially...</h2>';
78 $parent->printVar('_private');
79 $child->printVar('_private');
80
81 // Attempt to modify $_private and reprint:
82 echo '<h2>Attempting to modify $parent->_private...</h2>';
83 $parent->_private = 'modified';
84 $parent->printVar('_private');
85 $child->printVar('_private');
86
87 // Delete the objects:
88 unset($parent, $child);
89
90 ?>
91 </body>
92 </html>


2. Begin declaring the Test class:

class Test {
public $public = 'public';
protected $protected = 'protected';
private $_private = 'private';

This class contains three attributes, one of each type. To make things even more obvious, the name and value of each attribute match its visibility.

As a convention, private variable names are often begun with an underscore. This is commonly done in many OOP languages, although it is not required.

3. Add a printVar() method and complete the class:

function printVar($var) {
echo "<p>In Test, \$$var: '{$this->$var}'.</p>";
}
} // End of Test class.

The printVar() method prints the value of a variable whose name it receives as an argument. It will print the attribute’s name and value out like this:

In Test, $public: 'public'.

The \$$var will end up printing a dollar sign followed by the value of $var (the argument). The $this->$var code will be evaluated as $this->public, $this->protected, and $this->private so that it can access the class attributes.

4. Create a class that extends Test:

class LittleTest extends Test {
function printVar($var) {
echo "<p>In LittleTest, \$$var: '{$this->$var}'.</p>";
}
} // End of LittleTest class.

The LittleTest class, as an extension of Test, will inherit the $public and $protected attributes. It will not have the $_private attribute, as that variable’s visibility is private, meaning it cannot be inherited.

This class will override the printVar() method, changing the printed text slightly.

5. Create an object of each type:

$parent = new Test();
$child = new LittleTest();

6. Print the current value of the $public variable by calling the printVar() method:

echo '<h1>Public</h1>';
echo '<h2>Initially...</h2>';
$parent->printVar('public');
$child->printVar('public');

Because the $public variable is public, it can be accessed by either class. You could also access it outside of the class using $parent->public or $child->pubic.

7. Modify the Test $public attribute and reprint:

echo '<h2>Modifying $parent->public...</h2>';
$parent->public = 'modified';
$parent->printVar('public');
$child->printVar('public');

Because $public has public visibility, it can be accessed (and therefore modified) anywhere image. You should note that these lines only change the value of the $public attribute in the $parent object. The $child object’s $public variable still has the original value (because the $public attribute is represented as a separate entity in each object).

image

image Public variables can be accessed, and modified, anywhere.

8. Repeat Steps 6 and 7 for the protected variable:

echo '<hr><h1>Protected</h1>';
echo '<h2>Initially...</h2>';
$parent->printVar('protected');
$child->printVar('protected');
echo '<h2>Attempting to modify $parent->protected...</h2>';
$parent->protected = 'modified';
$parent->printVar('protected');
$child->printVar('protected');

As you’ll see when you run this script image, you can access the $protected variable from within either class. But you cannot access it (which also means you cannot modify it) from outside either class, including through an object of that class type. Doing so causes a fatal error.

image

image Attempting to modify the value of the protected variable using the syntax $obj->var results in a fatal error (which is bad).

9. Complete the page:

unset($parent, $child);
?>
</body>
</html>

Ignore the rest of what you see in Script 5.4 for now, as I’m working you through a process!

10. Save the file as visibility.php, place it in your Web directory, and test in your Web browser image.

This is one of those rare times where I actually want you to see the error, so that you may better understand visibility. A public class member can be accessed anywhere, including outside of a class (i.e., through an object). A protected member can only be accessed in the class or in derived classes. Attempting to access the member elsewhere results in a fatal error. Thus, a protected class member is more insulated.

11. Comment out this line:

$parent->protected = 'modified';

This is the line that caused the fatal error, so let’s make it inert in order to try something new.

12. Before unsetting the objects, repeat Steps 6 and 7 for the private attribute.

echo '<hr><h1>Private</h1>';
echo '<h2>Initially...</h2>';
$parent->printVar('_private');
$child->printVar('_private');
echo '<h2>Attempting to modify $parent->_private...</h2>';
$parent->_private = 'modified';
$parent->printVar('_private');
$child->printVar('_private');

To finish this example, let’s look at where you can access private class members.

13. Save the file and retest image.

image

image Attempting to refer to $this->_private within the LittleTest class—which is what happens when you call $child->printVar('_private')—creates a notice, as the class does not contain that attribute (because it neither inherited one nor defined one itself). Attempting to refer to $parent->_private results in a fatal error.

As you can see in the figure, not even the $child object, which is an instance of the inherited LittleTest class, can access $_private. And the script cannot refer to $parent->_private, which, again, causes a fatal error.


Tip

Many programmers would argue that all attributes should be protected or private, so they are never directly accessible outside of the class. You would then write “get” and “set” methods as an interface for accessing them when needed.



Tip

A method designed to return an attribute’s value is called a getter or an accessor. A method designed to assign a value to an attribute is called a setter or a mutator.



Tip

Think of access control as a firewall between your class and everything else, using visibility to prevent bugs and other inappropriate behavior (e.g., being able to change class attributes).



Tip

If a class has a method that should only ever be called by the class itself, it should also be marked either protected or private.


Using the Scope Resolution Operator

OOP has some of its own operators, as you’ve already seen with ->, used by objects to access their members. Another is the scope resolution operator: the combination of two colons together (::). It’s used to access members through classes, not objects:

ClassName::methodName();
ClassName::propertyName;

There are two places in which this construct is used:

• Within classes, to avoid confusion when inherited classes have the same attributes and methods

• Outside of classes, to access members without first creating objects

Outside of a class, you will need to specify the class name, as in the previous code. Within a class, however, are special keywords you should use. Whereas you can use $this within a class to refer to the current object instance, the keyword self is a reference to the current class:

class SomeClass {
function _ _construct() {
self::doThis();
}
protected function doThis() {
echo 'done!';
}
}

In that code, self::doThis() will invoke the doThis() method of the current class. (As a side note on understanding visibility, the doThis() function defined in SomeClass can only be called by other methods within SomeClass or by methods within inherited classes, because doThis() is defined as protected.)

To refer to a member of a parent class, use the scope resolution operator with the keyword parent:

class SomeOtherClass extends SomeClass{
function _ _construct() {
parent::doThis();
}
}

For the most part, you’ll use the scope resolution operator to access overridden methods. You’ll also use it with static and constant class members, two topics yet to be discussed. In the meantime, as a simple demonstration of how you might use this new information, I’ll touch up the Pet, Dog, and Cat classes.

To use the scope resolution operator

1. Open pets2.php (Script 5.3) in your text editor or IDE.

2. Modify the Pet constructor so that the animals immediately sleep (Script 5.5):

function _ _construct($pet_name) {
$this->name = $pet_name;
self::sleep();
}

It seems that many animals go to sleep as one of the first things they do. By placing this new line in the constructor, you ensure that the sleep() method is called as soon as the object is created.

Script 5.5. The scope resolution operator makes it easy to refer to the correct overridden method.


1 <!doctype html>
2 <html lang="en">
3 <head>
4 <meta charset="utf-8">
5 <title>Pets</title>
6 <link rel="stylesheet" href="style.css">
7 </head>
8 <body>
9 <?php # Script 5.5 - pets3.php
10 // This page defines and uses the Pet, Cat, and Dog classes.
11
12 # ***** CLASSES ***** #
13
14 /* Class Pet.
15 * The class contains one attribute: name.
16 * The class contains four methods:
17 * - _ _construct()
18 * - eat()
19 * - sleep()
20 * - play()
21 */
22 class Pet {
23 public $name;
24 function _ _construct($pet_name) {
25 $this->name = $pet_name;
26 self::sleep();
27 }
28 function eat() {
29 echo "<p>$this->name is eating.</p>";
30 }
31 function sleep() {
32 echo "<p>$this->name is sleeping.</p>";
33 }
34 function play() {
35 echo "<p>$this->name is playing.</p>";
36 }
37 } // End of Pet class.
38
39 /* Cat class extends Pet.
40 * Cat overrides play().
41 */
42 class Cat extends Pet {
43 function play() {
44
45 // Call the Pet::play() method:
46 parent::play();
47
48 echo "<p>$this->name is climbing.</p>";
49 }
50 } // End of Cat class.
51
52 /* Dog class extends Pet.
53 * Dog overrides play().
54 */
55 class Dog extends Pet {
56 function play() {
57
58 // Call the Pet::play() method:
59 parent::play();
60
61 echo "<p>$this->name is fetching.</p>";
62 }
63 } // End of Dog class.
64
65 # ***** END OF CLASSES ***** #
66
67 // Create a dog:
68 $dog = new Dog('Satchel');
69
70 // Create a cat:
71 $cat = new Cat('Bucky');
72
73 // Create an unknown type of pet:
74 $pet = new Pet('Rob');
75
76 // Feed them:
77 $dog->eat();
78 $cat->eat();
79 $pet->eat();
80
81 // Nap time:
82 $dog->sleep();
83 $cat->sleep();
84 $pet->sleep();
85
86 // Have them play:
87 $dog->play();
88 $cat->play();
89 $pet->play();
90
91 // Delete the objects:
92 unset($dog, $cat, $pet);
93
94 ?>
95 </body>
96 </html>


3. Modify the Cat play() method so that it calls Pet play():

function play() {
parent::play();
echo "<p>$this->name is climbing.</p>";
}

The play() method in the Pet and Cat classes do slightly different things. The Pet method says that the object is playing. The Cat method says specifically what kind of play. To have the functionality of both methods, call parent::play() within the Cat method.

4. Repeat Step 3 for the Dog class:

function play() {
parent::play();
echo "<p>$this->name is fetching.</p>";
}

5. Save the file as pets3.php, place it in your Web directory, and test in your Web browser image.

image

image The modified code (Script 5.5) now calls the Pet play() method each time a Cat or Dog plays.


Tip

In Dog and Cat, you could also use the code Pet::play(). But by using parent::play(), you minimize the chance of future problems should the class definitions change.



Tip

You will often see documentation use the ClassName::methodName() syntax. When you see this, it’s not suggesting that you should call the method this way, but rather that methodName() is part of ClassName.


Creating Static Members

Static class attributes are the class equivalent of static function variables (see Chapter 1, “Advanced PHP Techniques”). To recap, a static function variable remembers its value each time a function is called image:

function test() {
static $n = 1;
echo "$n<br>";
$n++;
}
test();
test();
test();

image

image A static variable in a function retains its value over multiple calls (in the same script).

As the figure shows, each call of the test() function increments the value of $n by 1. If $n was not declared as static, each call to the function would print the number 1.

With static class attributes, the concept is just the same except that a static variable is remembered across all instances of that class (across all objects based on the class). To declare a static attribute, use the static keyword after the visibility indicator:

class SomeClass {
public static $var = 'value';
}


Class Constants

Class constants are like static attributes in that they are accessible to all instances of that class (or derived classes). But as with any other constant, the value can never change. Class constants are created using the const keyword, followed by the name of the constant (without a dollar sign), followed by the assignment operator and the constant’s value:

class SomeClass {
const PI = 3.14;
}

Constants can only be assigned a value like in that example. The value cannot be based on another variable, and it can’t be the result of an expression or a function call.

Constants, like static attributes, also cannot be accessed through the object. You cannot do this:

$obj->PI

or

$obj::PI

But you can use ClassName::CONSTANT_NAME (e.g., SomeClass::PI) anywhere. You can also use self::CONSTANT_NAME within the class’s methods.


Static variables differ from standard attributes in that you cannot access them within the class using $this. Instead, you must use self, followed by the scope resolution operator (::), followed by the variable name, with its initial dollar sign:

class SomeClass {
public static $counter = 0;
function _ _construct() {
self::$counter++
}
}

The preceding code creates a counter for how many objects of this class exist. Each time a new object is created:

$obj = new SomeClass();

$counter goes up by one.

Static methods are created in much the same way:

class SomeClass {
public static function doThis() {
// Code.
}
}

Once you’ve declared a class member as static, you can access it using the class name, without creating an object. Again, the scope resolution operator is used to refer to members of a class:

class SomeClass {
public static $counter = 0;
public static function doThis() {
// Code.
}
}
echo SomeClass::$counter; // 0
SomeClass::doThis();

(In fact, not only can you access static members without creating an object, static properties cannot be accessed through an object, although static methods can be.)

To play this out, let’s create a new Pet class that uses a static attribute and a static method. The attribute will be used to count the number of pets in existence. The static method will return the number of pets. As this is just a demonstration of this new concept, no other methods will be created.

To create static members

1. Begin a new PHP script in your text editor or IDE, to be named static.php, starting with the HTML (Script 5.6):

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Static</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<?php # Script 5.6 - static.php

2. Begin declaring the Pet class:

class Pet {
protected $name;
private static $_count = 0;

The class still has the $name attribute, but it’s now marked as protected so that only this and derived classes can access it. The $_count variable, which is initialized as 0, is both private and static. By making it private, only this class can access it, which is smart, because you don’t want any other code to be able to change the counter. By making $_count static, you ensure that it retains its value for all instances of Pet or any derived classes.

3. Create the constructor:

function _ _construct($pet_name) {
$this->name = $pet_name;
self::$_count++;
}

Script 5.6. A static attribute and a static method are used to count the number of pets created in this script.


1 <!doctype html>
2 <html lang="en">
3 <head>
4 <meta charset="utf-8">
5 <title>Static</title>
6 <link rel="stylesheet" href="style.css">
7 </head>
8 <body>
9 <?php # Script 5.6 - static.php
10 // This page defines and uses the Pet, Cat, and Dog classes.
11
12 # ***** CLASSES ***** #
13
14 /* Class Pet.
15 * The class contains two attributes:
16 * - protected name
17 * - private static _count
18 * The class contains three methods:
19 * - _ _construct()
20 * - _ _destruct()
21 * - public static getCount()
22 */
23 class Pet {
24
25 // Declare the attributes:
26 protected $name;
27 private static $_count = 0;
28
29 // Constructor assigns the pet's name
30 // and increments the counter:
31 function _ _construct($pet_name) {
32
33 $this->name = $pet_name;
34
35 // Increment the counter:
36 self::$_count++;
37
38 }
39
40 // Destructor decrements the counter:
41 function _ _destruct() {
42 self::$_count--;
43 }
44
45 // Static method for returning the counter:
46 public static function getCount() {
47 return self::$_count;
48 }
49
50 } // End of Pet class.
51
52 /* Cat class extends Pet. */
53 class Cat extends Pet {
54 } // End of Cat class.
55
56 /* Dog class extends Pet. */
57 class Dog extends Pet {
58 } // End of Dog class.
59
60 /* Ferret class extends Pet. */
61 class Ferret extends Pet {
62 } // End of Ferret class.
63
64 /* PygmyMarmoset class extends Pet. */
65 class PygmyMarmoset extends Pet {
66 } // End of PygmyMarmoset class.
67
68 # ***** END OF CLASSES ***** #
69
70 // Create a dog:
71 $dog = new Dog('Old Yeller');
72
73 // Print the number of pets:
74 echo '<p>After creating a Dog, I now have ' . Pet::getCount() . ' pet(s).</p>';
75
76 // Create a cat:
77 $cat = new Cat('Bucky');
78 echo '<p>After creating a Cat, I now vhave ' . Pet::getCount() . ' pet(s).</p>';
79
80 // Create another pet:
81 $ferret = new Ferret('Fungo');
82 echo '<p>After creating a Ferret, I now have ' . Pet::getCount() . ' pet(s).</p>';
83
84 // Tragedy strikes!
85 unset($dog);
86 echo '<p>After tragedy strikes, I now have ' . Pet::getCount() . ' pet(s).</p>';
87
88 // Pygmy Marmosets are so cute:
89 $pygmymarmoset = new PygmyMarmoset('Toodles');
90 echo '<p>After creating a Pygmy Marmoset, I now have ' . Pet::getCount() . ' pet(s).</p>';
91
92 // Delete the objects:
93 unset($cat, $ferret, $pygmymarmoset);
94
95 ?>
96 </body>
97 </html>


The constructor still assigns the name to the $name attribute, but now it also increments the counter. Note the unique syntax for referring to a static attribute.

Every time an object of type Pet or of a derived type is created, this constructor gets called. So for every qualifying object, $count is incremented.

4. Create the destructor:

function _ _destruct() {
self::$_count--;
}

Just as the constructor should increase the value of $_count, the destructor should decrease it. Every time an object of a qualifying type (Pet or a derived class) is destroyed, this destructor is called.

5. Create the static method and complete the class:

public static function getCount() {
return self::$_count;
}
} // End of Pet class.

The getCount() method is public and static. This means that it’s available to be called anywhere. It returns the value of $_count.

6. Create a Cat class:

class Cat extends Pet {
}

Since the focus here is on the static members, the derived classes don’t need to do anything.

7. Create a couple more subclasses:

class Dog extends Pet {
}
class Ferret extends Pet {
}
class PygmyMarmoset extends Pet {
}

8. Create a new object and print the number of pets:

$dog = new Dog('Old Yeller');
echo '<p>After creating a Dog, I now have ' . Pet::getCount() . ' pet(s).</p>';

When $dog is created, the Pet constructor is called, incrementing $_count to 1. To return this value, invoke Pet::getCount().

To avoid confusion, I’ll point out that the Pet constructor is called when making an object of type Dog because Dog does not have its own constructor.

9. Create a couple more pets:

$cat = new Cat('Bucky');
echo '<p>After creating a Cat, I now have ' . Pet::getCount() . ' pet(s).</p>';
$ferret = new Ferret('Fungo');
echo '<p>After creating a Ferret, I now have ' . Pet::getCount() . ' pet(s).</p>';

10. Have the unthinkable happen (my condolences):

unset($dog);
echo '<p>After tragedy strikes, I now have ' . Pet::getCount() . ' pet(s).</p>';

When a Dog (or any other subclass) object is destroyed (here using unset()), the Pet destructor is invoked, subtracting 1 from $_count. (Again, the Pet destructor is called because no derived class has its own destructor.)

11. Recover by getting another pet:

$pygmymarmoset = new PygmyMarmoset('Toodles');
echo '<p>After creating a Pygmy Marmoset, I now have ' . Pet::getCount() . ' pet(s).</p>';

12. Complete the page:

unset($cat, $ferret,
$pygmymarmoset);
?>
</body>
</html>

13. Save the file as static.php, place it in your Web directory, and test in your Web browser image.

image

image As the Pet class contains a static attribute, it can be used to count the number of objects created from derived classes.


Tip

If you did want to have overridden constructors and destructors in the derived classes in this example (Cat, Dog, et al.), you would need them to call the Pet constructor and destructor in order to properly manage the page count. You would do so by adding parent::_ _construct()and parent::_ _destruct() to them.



Tip

Static methods are almost always public because they can’t be called through an object.



Tip

Static attributes and methods are also sometimes called class attributes and class methods (indicating they are meant to be accessed by referencing the class itself, not through an object).



Tip

The special variable $this, which always refers to the current object, is not available inside a static method, because static methods are meant to be invoked without using an object.


Review and Pursue

If you have any problems with these sections, either in answering the questions or pursuing your own endeavors, turn to the book’s supporting forum (www.LarryUllman.com/forums/).

Review

• What is inheritance? How do you implement inheritance in PHP code? (See page 152.)

• What is polymorphism? (See page 151.)

• What does it mean to override a method? How do you do that? (See page 161.)

• What is access control or visibility? What are the three levels of visibility and what do they mean? (See page 165.)

• What is the scope resolution operator? What are some of its uses? (See page 172.)

• What is a static class attribute? How do you reference a static class attribute within a class? What is a static class method? How do you invoke a static class method? (See pages 176 and 177.)

Pursue

• In situations where a class is both defined and used in the same script, break it into two (or more) scripts, separating the class definitions from their usage.

• Complete the UML diagrams for any of the examples in this chapter.

• Add phpDocumentor-style comments to any of the class definitions and scripts in this chapter.

• Once you’ve become comfortable with more advanced OOP, look into the subject of overloading a method.