Object-oriented PHP :: A guide for fellow ISys junkies

I’m currently studying Information Systems at Brigham Young University where the main language being taught to students is Java, a very object-oriented language. Some students, after having learned Java, have difficulty making the jump from Java to PHP for one reason or another. While I do not profess to have reached the level of Zen Master in PHP, I do hope I can provide some insight in a way that fellow ISys junkies can take what they’ve learned and pick up PHP if they want. As always, I’m open to improvements/corrections in my code, questions, complaints, I-could-do-it-better-than-yous, whatever. So here we go!

First off, PHP has not always been an object-oriented language and has yet to become a “pure” object-oriented language (and probably won’t ever be — it’s good at filling a distinct niche somewhere between scripting and enterprise application development). For example, you can still code in PHP without using objects at all. Also, some object-oriented features that are available in other languages are still not available in PHP, like overloading (although supported, it’s not implemented the “standard” way we’re used to in other languages) and namespaces. There are hacks that can allow for such functionality, but we’ll leave those alone for now. In any case, PHP is still my friend and it can be yours too.

Before we get into the OO (object-oriented) code, I want to give a really short intro to a few PHP basics. If you’ve already played with PHP, you’re probably safe to move onto the next page.

Defining a variable

In PHP, a variable is defined the following way:

$myVar = "my string";

Notice I did not have to declare $myVar as a string before setting its value. If you were to then set $myVar = 6, no conversion code on your part would be necessary; PHP would convert it from a string to an integer for you. That’s one of the beauties and the uglies of PHP, take it or leave it. Every variable name must be preceded with a dollar sign (we’ll see somewhat of an exception on the next page.) PHP is also case-sensitive, so be sure you take care of capitalization.

Outputting to the browser

To output something on the user’s browser, use the echo command:

echo "A test.";
echo $myVar;

The first line prints A test. while the second line prints whatever is stored in your $myVar variable.

Concatenation and variable interpretation

Because we precede every variable name with a dollar sign, PHP can easily tell what’s a variable and what’s not. Let’s look at some code:

$name = "John Doe";
echo "Hello, $name! Welcome to my website.<br />";
echo "Hello, " . $name . "! Welcome to my website.<br />";
echo 'Hello, $name! Welcome to my website.<br />';
echo 'Hello, ' . $name . '! Welcome to my website.<br />';

This outputs the following:

Hello, John Doe! Welcome to my website.
Hello, John Doe! Welcome to my website.
Hello, $name! Welcome to my website.
Hello, John Doe! Welcome to my website.

In our code, using double-quotes tells PHP to interpret our variables and spit out their values. On the other hand, using single-quotes tells PHP not to interpret variables, which is why our third output line came out the way it did. Finally, using a period (.) lets you concatenate the same way that you would using the plus sign (+) in Java. Usually we’ll want to use our first method unless there’s a compelling reason not to.

Now to the fun stuff…

Before we hop into objects, you must be made aware of the var_dump() function. Basically, once you have an object variable (or any variable for that matter), you can see everything inside it like this:

var_dump($myObject);

Just a tip for debugging.

Our first class

To start off, let’s create our first class.

class MyClass {
}

Yup, that’s it. But we can’t do much with it, so let’s add a class variable and a function.

class MyClass {
public $woot = "Woot!";

public function sayWoot() {
echo $this->woot;
}
}

In this case, we have a class variable named $woot and a function named sayWoot(), both of which are declared public, meaning they are accessible from outside the class. Notice that inside the sayWoot() function, we reference the class variable using $this-> and then the class variable name without the dollar sign. The only case that I can think of where a variable name is not preceded by a dollar sign is when you are referencing a class variable like we are here. Note that $this-> is similar to this. in Java. It’s a pseudo-variable that says “reference myself” if you’re inside an object. There’s also an identifier self::, which is used in a non-object context. To make the connection from what you learned in INTEX II, a BO would be an “object context” where you instantiate a container of information, move it around, etc. On the other hand, a DAO would be a “non-object context” where you just want to call one function and be done with it; there’s no reason to instantiate it as an object.

Since the writing of this tutorial, it’s been brought to my attention that we didn’t use static methods in our DAO class during INTEX. Still, I prefer using static methods. See the comments at the very bottom of this page for more info.

We’ll learn about self:: later on; just know that both exist and that there’s a difference between the two.

Now, let’s instantiate (make an object from) the class and do something with it:

$myObject = new MyClass();
echo $myObject->woot;
$myObject->sayWoot();

$myObject is now an instance of the MyClass class. Both our second and third lines of code output Woot! as you might expect. On the second line, we had to use the echo command because we only had the woot variable value and we wanted to spit the value out to the browser. On our third line, we did not have to use the echo command because we called the sayWoot() function, which contained its own echo command internally.

Returning values

What if we want to return a value from our function? Let’s tweak our class a little bit:

class MyClass {
public $woot = "Woot!";

public function sayWoot() {
return $this->woot;
}
}

$myObject = new MyClass();
$returnValue = $myObject->sayWoot();
echo $returnValue;

As you can see, all it really takes is the word return and then whatever you want to return. No other changes are necessary. One important thing to notice is that nowhere do we declare the type of variable that we’re returning. This means whenever we call our function we must understand exactly what type of variable it’s returning so we can deal with it appropriately later on down the line.

One other small note. If I wanted to immediately output to the browser whatever the function returned, there’s no need to store the return value into a variable like I did. Instead, we could have just as easily said:

$myObject = new MyClass();
echo $myObject->sayWoot();

Separating your code

In case you’re wondering, you can put both your class code and your non-class code in the same file (you can also put the code for multiple classes in the same file.) For example, I have a file called index.php that has the following:

class MyClass {
public $woot = "Woot!";

public function sayWoot() {
echo $this->woot;
}
}

$myObject = new MyClass();
echo $myObject->woot;
$myObject->sayWoot();

On the other hand, when you actually start making a real application, you’ll want to split the code into different files. So index.php would have the following:

require_once($_SERVER['DOCUMENT_ROOT'] . "/MyClass.php");
$myObject = new MyClass();
echo $myObject->woot;
$myObject->sayWoot();

Note that the require_once() function requires the full file path to the file you’re including. $_SERVER[‘DOCUMENT_ROOT’] will retrieve everything in the path down to the root directory of your website, so that helps out. MyClass.php would have:

class MyClass {
public $woot = "Woot!";

public function sayWoot() {
echo $this->woot;
}
}

Using a constructor

While PHP doesn’t require a constructor, you can still use one. Here’s an example:

class MyClass {
public $woot = "Woot!";

function __construct() {
$this->sayWoot();
}

private function sayWoot() {
echo $this->woot;
}
}

The function name __construct() that I used for the constructor was not chosen randomly. PHP requires that you name your constructor exactly like that. You may see code with a constructor that just uses the same name as the class (like in Java,) but that is now deprecated and has been replaced by the function name __construct(). That’s two underscores.

So, with our constructor set, we can now instantiate an object and it will automatically spit out Woot!.

$myObject = new MyClass();

Notice that the constructor really just calls the sayWoot() function. It could’ve done echo $this->woot; itself, but I just wanted to let you see how to reference another class function. It’s pretty similar to referencing the class variables. Also notice that I declared the sayWoot() function as private, so it can’t be called from outside the class.

Passing arguments

So what if we wanted to pass a variable as an argument into a constructor or function? Let’s check it out:

class MyClass {

function __construct($whatToSay) {
$this->saySomething($whatToSay);
}

private function saySomething($whatToSay) {
echo $whatToSay;
}
}

$myObject = new MyClass("Woot!");

It’s pretty similar to Java, so there’s not a whole lot that’s new. Again, the constructor could’ve echoed $whatToSay itself, but I just wanted to show you a little variety.

Passing by reference vs. value

There are two ways to pass an argument to a function: (1) by reference and (2) by value. Let’s look at a couple examples and then I’ll explain what’s going on.

Passing argument by value:

class ValueChanger {

public function changeTheValue($variableToChange) {
$variableToChange = 'new value';
}

}

$valueChanger = new ValueChanger();
$myVar = 'old value';
$valueChanger->changeTheValue($myVar);
echo $myVar;

Passing argument by reference:

class ValueChanger {

public function changeTheValue(&$variableToChange) {
$variableToChange = 'new value';
}

}

$valueChanger = new ValueChanger();
$myVar = 'old value';
$valueChanger->changeTheValue($myVar);
echo $myVar;

As denoted, the first example passes the argument by value while the second example passes the argument by reference. The difference in code is that the second example has an ampersand (&) right before the $variableToChange argument. That’s it. The difference in output is that the first example (passing by value) outputs ‘old value’ while the second example (passing by reference) outputs ‘new value.’ Why?

In the first example, when the changeTheValue() function gets called, PHP creates a copy of our $myVar variable and passes the new copy to the function; once we’re inside the function we’re no longer dealing with our original $myVar variable. Therefore, when we set $variableToChange (the copy that’s inside the function) to ‘new value,’ it doesn’t affect our $myVar variable (the copy that’s outside the function.) In the end, $myVar goes unchanged and our script outputs ‘old value.’

In the second example, when the changeTheValue() function gets called, PHP passes a reference of our $myVar variable to the function. This means, once inside the function, when we set $variableToChange to a new value, we’re also setting $myVar to that new value simultaneously; $variableToChange is merely a reference to $myVar. Due to the reference, $myVar gets set to ‘new value’ and therefore our script outputs ‘new value.’

Protecting your class variables

You’ve probably heard that PHP is a very relaxed language, and that is definitely true. Let’s take a look at an example of the extremely relaxed possibilities of PHP.

class MyClass {
}

$myObject = new MyClass();
$myObject->whatToSay = "Woot!";
echo $myObject->whatToSay;

The crazy thing is that it works. Even though MyClass is initially just an empty class, we’re still able to set random variables inside it and pull them out again. This probably isn’t the greatest practice and could lead to some extremely frustrating debugging later on. To restrict getting and setting variables to only the variables which have been declared in the class as publicly available, we will take advantage of the PHP __get() and __set() functions. Here’s how:

class MyClass {

public function __get($var_name) {
trigger_error("Class '" . get_class($this) . "' does not have member '$var_name' publicly available", E_USER_ERROR);
}

public function __set($var_name, $var_value) {
trigger_error("Class '" . get_class($this) . "' does not have member '$var_name' publicly available", E_USER_ERROR);
}

}

$myObject = new MyClass();
$myObject->whatToSay = "Woot!";
echo $myObject->whatToSay;

To clarify, the __get() and __set() functions are what are called “magic functions” in PHP. Just like the __construct() method (which is also a magic function,) they are “magically” called when they should be. In the case of the __get() and __set() methods, they are magically called when something is trying to get or set a class variable (respectively) that has not been declared as public. When I mistakingly try to get or set a class variable that hasn’t been declared as public, I would prefer my application throw an error and kill my application rather than possibly dealing with dirty data, so in my __get() and __set() functions I throw a fatal error using PHP’s triggor_error() function. In our example, we try to set a class variable named whatToSay that doesn’t actually exist, therefore our action matches the criteria of the __set() magic function and it gets triggered. When we execute our page, we’ll get the following error:

Fatal error: Class 'MyClass' does not have member 'whatToSay' publicly available in /mnt/backside/vol/navyblue/spunky/aaronius/aaronhardy.com/php_sandbox/index.php on line 7

Again, this is what I want. I’d much rather know now than later that I did something I shouldn’t have.

Note that if you want to protect your variables as I’ve shown, you’ll want to use our __get() and __set() methods on every class that you create. For simplicity, I may not use them in later examples in this tutorial, but I’d still recommend you use them in your actual production code. Also keep in mind that most PHP programmers do not implement the __get() and __set() methods, but that doesn’t mean it’s a bad idea.

Protecting your class variables — Stepping it up a notch

While the protection we’ve already given our class variables is better than it was, oftentimes it’s just not enough. Remember during INTEX II when we had to write all the getters and setters for our class variables? This technique is called “encapsulation” or “information hiding,” and there are good reasons for it. Most importantly in my view, encapsulation can be used to validate and/or modify arguments before setting class variables or for doing similar modification before sending class variables back out of the object. For more information on encapsulation, I’d highly suggest reading John Reynold’s article entitled How not to teach programming: Getter and Setter Methods. I won’t go into depth about when encapsulation should or should not be used as it’s still a fairly debated issue, but let’s just see how it works in PHP:

class MyClass {

private $whatToSay;

public function setWhatToSay($whatToSay) {
$this->whatToSay = $whatToSay;
}

public function getWhatToSay() {
return $this->whatToSay;
}

public function __get($var_name) {
trigger_error("Class '" . get_class($this) . "' does not have member '$var_name' publicly available", E_USER_ERROR);
}

public function __set($var_name, $var_value) {
trigger_error("Class '" . get_class($this) . "' does not have member '$var_name' publicly available", E_USER_ERROR);
}

}

$myObject = new MyClass();
$myObject->setWhatToSay("Woot!");
echo $myObject->getWhatToSay();

It’s pretty similar to Java. In essence, we’re keeping our class variables private and forcing any modification of those variable through the getter and setter functions we have set up. Notice that I’ve still chosen to use my magic __get() and __set() functions because I prefer that my classes not be used for unintended purposes.

Protecting your class variables — Type hinting

PHP 5 introduced one other possibility of protecting your class variables called “type hinting.” With type hinting, we are forcing our arguments to be of a certain class. Let’s look at an example and then I’ll explain what’s going on:

class Dog {

public function bark() {
echo "Bark!";
}

}

class Cat {

public function meow() {
echo "Meow!";
}
}

class DogOwner {

public function makeTheDogBark(Dog $dog) {
$dog->bark();
}

}

$dog = new Dog();
$cat = new Cat();
$dogOwner = new DogOwner();

$dogOwner->makeTheDogBark($dog);
$dogOwner->makeTheDogBark($cat);

First things first, notice I didn’t use the __get() and __set() functions like we did before. That’s just for simplicity in showing you code, but in your production code I would still recommend you use them.

So we have three classes: Dog, Cat, and DogOwner. Within DogOwner, we have a method makeTheDogBark and inside the parenthesis it says Dog $dog. The Dog part is type hinting. It says only let variables of type Dog in for this argument. Let’s now focus on the very last two lines of our code. On the first, we try passing a dog in. The output is Bark!. On the second line, we try passing in a cat. The output in this case is:

Catchable fatal error: Argument 1 passed to DogOwner::makeTheDogBark() must be an instance of Dog, instance of Cat given, called in /mnt/backside/vol/navyblue/spunky/aaronius/aaronhardy.com/php_sandbox/index.php on line 31 and defined in /mnt/backside/vol/navyblue/spunky/aaronius/aaronhardy.com/php_sandbox/index.php on line 21

As you might have expected, our code broke because we tried passing it a cat when it was expecting a dog. Again, I’d much rather know now than later that I did something I shouldn’t have. As the error output implies, this error is catchable, which means you can do error handling with a try-catch structure just like you did in Java if you wish to handle the error in a more elegant fashion.

One final note on type hinting is that it only supports class types and arrays. In other words, you can’t hint that an argument should be a string or an integer. This is a fairly stark contrast to Java, C#, VB, and other languages that require that you declare the argument type no matter what it is.

Static keyword

As you learned in programming class, the static keyword allows you to access a class member without being in an object context. In other words, oftentimes you have a class that really only provides functionality; it’s not like a business object where you need to hold information temporarily, move the object around, pull the information back out, etc. A data access object comes to mind (DAO), but if you keep an eye out you’ll find that there are many times when you only need to use a class outside of an object context. The benefit of recognizing these cases is that you don’t have to instantiate the class every time you want to use it. So instead of calling bark() like this:

$dog = new Dog();
$dog->bark();

You can just call it like this:

Dog::bark();

Notice the double-colon. This is used instead of the arrow (->) when we’re not dealing with an instantiated object. So what do we need to make this possible? In PHP, very little. Take a look:

class Dog {

public function bark() {
echo "Bark!";
}

}

Dog::bark();

It works. I never had to instantiate the class and the code in my Dog class really isn’t any different from just your ordinary class. Again, this is another example of just how relaxed PHP is. So why should we be cautious about being this relaxed? Let’s add one element:

class Dog {

private $mySound = "Bark!";

public function bark() {
echo $this->mySound;
}

}

Dog::bark();

This throws the following error:

Fatal error: Using $this when not in object context in /mnt/backside/vol/navyblue/spunky/aaronius/aaronhardy.com/php_sandbox/index.php on line 25

Like the error says, we tried using $this-> outside of an object context (in other words, we’re not dealing with an instantiated object). So what if we change the $this-> to self::? To explain, self:: works just the same way as $this-> except it’s used outside of an object context and must be followed by a dollar sign when referring to a class variable. So, $this-> is used when we’re dealing with an instantiated object while self:: is used when we’re not dealing with an instantiated object. Here’s what I’m talking about:

class Dog {

private $mySound = "Bark!";

public function bark() {
echo self::$mySound;
}

}

Dog::bark();

While we’re getting closer, we now get this error:

Fatal error: Access to undeclared static property: Dog::$mySound in /mnt/backside/vol/navyblue/spunky/aaronius/aaronhardy.com/php_sandbox/index.php on line 25

In other words, it’s saying that in order to access a class variable from a non-object context, we must declare that class variable as static. So let’s make one more modification to our code by declaring our class variable as static:

class Dog {

private static $mySound = "Bark!";

public function bark() {
echo self::$mySound;
}

}

Dog::bark();

Woot! No errors and we got the dog to bark. So, what do we learn from our tests? A class variable cannot be accessed in a non-object context without being declared static. On the other hand, a function can be accessed from a non-object context (i.e., no object instantiated) without being declared static! Ambiguous, yes. Even crazier, a function can be accessed from an object context (i.e., using an object) even if it is declared static. Although this is supposed to be a PHP “feature,” this is fairly disgruntling and I hope PHP 6 provides a more standard, clear-cut approach. So then, what good does the static keyword do in relation to class methods? From a functional standpoint, I can’t see that it does any good, but I still use it. Why? Because if I know at the time I code the method that it should only be called from a non-object context, I want to always remind myself of that whenever I look at the method while I’m coding.

In PHP’s documentation on the static keyword, it states:

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

If this is true and my understanding is correct, then you should be able to crank up error reporting to report warnings and that should prevent you from calling a non-static method without an object. In my tests though, I received no errors.

So this would be my final code:

class Dog {

private static $mySound = "Bark!";

public static function bark() {
echo self::$mySound;
}

}

Dog::bark();

See the public static function part? The static there really isn’t doing anything for me except reminding me that I should only call the method from a non-object context.

Sorry to show what I consider some of the flaws of PHP, but I think it’s necessary if you actually want to know how to code it well and know why you’re coding it well.

One last note — if you ever see an error that says something about PAAMAYIM_NEKUDOTAYIM, it’s Hebrew and means “double colon.” It happens when you try using the double colon (::) within an object context, like so:

$dog = new Dog();
$dog::mySound;

Why did they make an error in Hebrew? I don’t know.

Interfaces

If you don’t remember what interfaces are, I’ll do my best to explain. Work with me here. Oddly enough, interfaces in programming have some similarities to interfaces in the real world. Take the buttons on my TV for example. Wait, no, take the buttons on my remote instead. You know I haven’t touched the buttons on the TV since I bought the thing. I need at minimum five buttons on my remote to ensure that I can enjoy the latest episodes of Mythbusters, Glenn Beck, and Cops: power button, channel up, channel down, volume up, and volume down. Sure, I’d love a TiVo with all it’s recording abilities, but I’m too cheap for that. Right now, the five buttons will do, but nothing less. Now, give me a remote for a different TV. As crazy as it is, I still have at least the five buttons that I need! Why? Because the manufacturer of the TV knows there are five operations that I have to have to qualify for my membership renewal in The Bedsore Club.

Interfaces in programming are similar. I have a class that can call the functions power(), channelUp(), channelDown(), volumeUp(), and volumeDown() of whatever class that you give me. I don’t know exactly what you’re going to do once I call those functions, but to make sure nothing breaks between my class and your class, I’m going to require that you expose those five functions: power(), channelUp(), channelDown(), volumeUp(), and volumeDown(). Here’s how:

// I declare the interface - notice there's no "class" word on the next line
interface Remote {
public function power();
public function channelUp();
public function channelDown();
public function volumeUp();
public function volumeDown();
}

// You implement the interface
class SonyRemote implements Remote {

public function power() {
// Do something
}

public function channelUp() {
// Do something
}

public function channelDown() {
// Do something
}

public function volumeUp() {
// Do something
}

public function volumeDown() {
// Do something
}

public function record() {
// Do something
}

}

$myRemote = new SonyRemote();

To implement the interface, you just have to follow your implementing class name with implements and then the interface’s class name (class SonyRemote implements Remote {.) Remind you of Java? Also, as you can see, SonyRemote contains at least every method that is defined in the Remote interface. I also threw another method in the SonyRemote class just so you can see that you can and most likely will add other methods in addition to those defined by the interface. If you by chance don’t implement all the methods required by the interface, you’ll see this error:

Fatal error: Class SonyRemote contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (Remote::power) in /mnt/backside/vol/navyblue/spunky/aaronius/aaronhardy.com/php_sandbox/index.php on line 73

Before moving on, there are a few other things we can learn from interfaces. Interfaces themselves cannot be instantiated. In other words, if I try to instantiate the Remote interface like this:

$myRemote = new Remote();

I get this:

Fatal error: Cannot instantiate interface Remote in /mnt/backside/vol/navyblue/spunky/aaronius/aaronhardy.com/php_sandbox/index.php on line 75

So, an interface is really just instruction to the programmer saying, hey, if you want me to use your class, it must have at least these functions. I then enforce that instruction by only accepting your classes if they have implemented the interface.

Another thing to know is that you can also enforce the number and types (see the section on type hinting) of arguments that the methods must have. Here’s an example:

// Declare the interface
interface Remote {
public function power(Dog $niner);
public function channelUp();
public function channelDown();
public function volumeUp();
public function volumeDown();
}

// Implement the interface
class SonyRemote implements Remote {

public function power(Cat $niner) {
// Do something
}

public function channelUp() {
// Do something
}

public function channelDown() {
// Do something
}

public function volumeUp() {
// Do something
}

public function volumeDown() {
// Do something
}

}

$myRemote = new SonyRemote();

The interface says that my Power() method must accept a dog, but my SonyRemote class’s Power() function is accepting a cat instead. I have no idea what a dog and cat have to do with power, but anyway we would get this error:

Fatal error: Declaration of SonyRemote::power() must be compatible with that of Remote::power() in /mnt/backside/vol/navyblue/spunky/aaronius/aaronhardy.com/php_sandbox/index.php on line 58

One last thing: you can implement multiple interfaces by separating the interface names with commas, like this:

class SonyRemote implements Remote, SomeOtherInterface

Easy enough. You’re well on your way.

Abstraction

Now that we’ve talked about interfaces, we’re prepared to talk about abstract classes. As you may remember, an abstract class defines the core identity of its descendants. In other words, if we’re dealing with a dog, a cat, a chicken, and a pig, they all have a few things in common: they eat, they poop, and they make sounds. Rather than making those methods for the dog class and then copying them over to the cat class, the chicken class, and the pig class, it’ll be much more convenient and easier to maintain if we use an abstract class. In this case, our abstract class could be called Animal. The different types of animals will then extend this class. Let’s see an example:

// Declare the abstract class
abstract class Animal {

protected $stomach;

public function eat($food) {
$this->stomach[] = $food;
echo "I ate $food<br />";
}

public function poop() {
echo "<p>Poo contents:";
echo "<ul>";
foreach($this->stomach as $poo_nugget) {
echo "<li>$poo_nugget</li>";
}
echo "</ul>";
unset($this->stomach);
}

public function makeSound($sound) {
echo "$sound<br />";
}

}

// Extend the abstract class
class Dog extends Animal {

public function catchFrisbee($frisbee) {
echo "I caught the $frisbee<br />";
}

public function doIHavePlaydohInMyStomach() {
if(in_array("play-doh", $this->stomach)) {
echo "I have play-doh in my stomach<br />";
} else {
echo "I don't have any play-doh in my stomach<br />";
}
}

}

// Extend the abstract class
class Cat extends Animal {

public function climbTree() {
echo "I'm climbing a tree<br />";
}

}

echo "<p><b>Dog actions</b><p>";
$myDog = new Dog();
$myDog->eat("dog food");
$myDog->eat("grass");
$myDog->catchFrisbee("red aerobie");
$myDog->eat("play-doh");
$myDog->doIHavePlaydohInMyStomach();
$myDog->poop();
$myDog->eat("a bone");
$myDog->makeSound("Bark!");
$myDog->doIHavePlaydohInMyStomach();
$myDog->eat("dog food");
$myDog->poop();

echo "<p><b>Cat actions</b><p>";
$myCat = new Cat();
$myCat->eat("fish");
$myCat->eat("cat food");
$myCat->makeSound("Meow");
$myCat->climbTree();
$myCat->poop();

As you can see, both Dog and Cat extend the abstract class Animal. The functions in Animal are common between both dogs and cats, which is why they are in the abstract class rather than the Dog or Cat; it allows us to re-use the code without having to duplicate it.

In the abstract class, there’s a class variable that’s “protected.” This is a visibility keyword like public or private. While public means anyone can access it and private means only the containing class can access it, protected means the containing class and any inheriting class can access it. In other words, our Animal class, our Dog class, and our Cat class can all access the $stomach variable because it is protected, but nothing outside these classes can currently access that variable directly. You can see an example of our Dog class accessing the $stomach variable in it’s doIHavePlaydohInMyStomach() function.

You know you want to see what that code outputs. I won’t make you wait in overbearing anxiety any longer:

Dog actions

I ate dog food
I ate grass
I caught the red aerobie
I ate play-doh
I have play-doh in my stomach

Poo contents:

  • dog food
  • grass
  • play-doh

I ate a bone
Bark!
I don’t have any play-doh in my stomach
I ate dog food

Poo contents:

  • a bone
  • dog food

Cat actions

I ate fish
I ate cat food
Meow
I’m climbing a tree

Poo contents:

  • fish
  • cat food

Before explaining what I call interface-esque abstraction, I need to mention something about abstract classes that’s rather insignificant but insightful none-the-less. Every class we’ve looked at so far can be an abstract class (can be extended by other classes) as it currently is — without any changes. Code-wise, we did not have to declare our Animal class as abstract in order to extend it from other classes. In fact, if we hadn’t declared the class as abstract, we still could have used it as an abstract class by extending it from our Dog class (like we did) or we could’ve instantiated it like a regular class. On the other hand, because we did declare the class as abstract, we now cannot instantiate it directly (we’d get an error saying “Cannot instantiate abstract class.”) As a rule of thumb, if I know a class will be extended at all, I’ll usually declare the class as abstract. I have yet to find a need for a class that needs to be both extended as an abstract class and also needs to be directly instantiated. I’m not saying there’s not a case for it, I just haven’t personally found one and I like the clarity of declaring classes I intend to be abstract as abstract. With that in mind, if you’re going to use what I call interface-esque abstraction (covered in the next section), it is at that point in time that you are actually required to declare your class abstract. Got it? Got it. If I completely lost you on this last paragraph, just brush it under the rug; it’s not a big deal. Declare your abstract classes as abstract like we did in our last example and you’ll be good. Onward.

Interface-esque abstraction

While abstract classes provide the functionality just mentioned, it can also be a type of interface. So, beside providing functions and variables to extending classes, it can also force extending classes to have certain functions in them, just like an interface does. Here’s how:

abstract class Animal {

protected $stomach;

abstract protected function shedHair();

public function eat($food) {
$this->stomach[] = $food;
echo "I ate $food<br />";
}

public function poop() {
echo "<p>Poo contents:";
echo "<ul>";
foreach($this->stomach as $poo_nugget) {
echo "<li>$poo_nugget</li>";
}
echo "</ul>";
unset($this->stomach);
}

public function makeSound($sound) {
echo "$sound<br />";
}

}

In this example, any class that extends Animal must have a shedHair() function defined.

PHP’s documentation explains it well: “When inheriting from an abstract class, all methods marked abstract in the parent’s class declaration must be defined by the child; additionally, these methods must be defined with the same (or a less restricted) visibility. For example, if the abstract method is defined as protected, the function implementation must be defined as either protected or public, but not private.”

Overriding inherited functions

At times, we’ll want to override a function that is inherited from an abstract class. That’s no problem. Let’s take a gander at another example:

abstract class Animal {

protected $stomach;

public function eat($food) {
$this->stomach[] = $food;
echo "I ate $food<br />";
}

public function poop() {
echo "<p>Poo contents:";
echo "<ul>";
foreach($this->stomach as $poo_nugget) {
echo "<li>$poo_nugget</li>";
}
echo "</ul>";
unset($this->stomach);
}

public function makeSound($sound) {
echo "$sound<br />";
}

}

// Extend the abstract class
class Dog extends Animal {

public function eat() {
parent::eat("bacon bits");
}

public function catchFrisbee($frisbee) {
echo "I caught the $frisbee<br />";
}

public function doIHavePlaydohInMyStomach() {
if(in_array("play-doh", $this->stomach)) {
echo "I have play-doh in my stomach<br />";
} else {
echo "I don't have any play-doh in my stomach<br />";
}
}

}

$myDog = new Dog();
$myDog->eat();

In the example, our Animal class is exactly the same as the first Animal class we cooked up a while back. What’s different is that we added the eat() function to our Dog class and by doing so we overrode the eat() function in the parent Animal class. We learn a couple other things from this example as well…

First, notice that the eat() function in our parent class (Animal) accepts one argument ($food). On the other hand, the overriding eat() function in our child class (Dog) accepts no arguments. In other words, the overriding function does not need to accept the same number and/or types of arguments as the function it’s overriding.

Second, notice the line that says parent::eat(“bacon bits”);. The parent:: operator, as you may have assumed, refers to the parent class — in this case, Animal. While I wasn’t required to call the parent’s eat() function at all, I just wanted to show you how to do it in case you came across the need in the future. In the same way, you could’ve called the parent’s poop() or makeSound() functions if you had the hankerin’.

Putting it all together

We’ve gone through a fairly thorough intro to object-oriented PHP, but to help solidify things we need a good example to integrate a lot that we’ve learned. What better way to do it than by recreating the framework you learned during INTEX II?

Here’s the folder structure I’m working with on my server:

/PHPSandbox
 /Includes
 /BO
 /DAO

And here are the files with their respective code:

/PHPSandbox/Includes/BO/ProductBO.php

<?
require_once($_SERVER['DOCUMENT_ROOT'] . "/PHPSandbox/Includes/StrictClass.php");
require_once($_SERVER['DOCUMENT_ROOT'] . "/PHPSandbox/Includes/DAO/ProductDAO.php");

class ProductBO extends StrictClass {

private $id;
private $sku;
private $description;
private $cost;
private $vendor;

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

public function setId($id) {
$this->id = $id;
}

public function getSku() {
return $this->sku;
}

public function setSku($sku) {
$this->sku = $sku;
}

public function getDescription() {
return $this->description;
}

public function setDescription($description) {
$this->description = $description;
}

public function getCost() {
return $this->cost;
}

public function setCost($cost) {
$this->cost = $cost;
}

public function getVendor() {
return $this->vendor;
}

// This should probably not ever get called outside of our BOs and DAOs.
// Most likely it will get set when calling VendorBO->addProduct()
public function setVendor($vendor) {
$this->vendor = $vendor;
}

public function save() {
ProductDAO::saveProduct($this);
}

}
?>

/PHPSandbox/Includes/BO/VendorBO.php

<?
require_once($_SERVER['DOCUMENT_ROOT'] . "/PHPSandbox/Includes/StrictClass.php");
require_once($_SERVER['DOCUMENT_ROOT'] . "/PHPSandbox/Includes/DAO/VendorDAO.php");

class VendorBO extends StrictClass {

private $id;
private $name;
private $address;
private $city;
private $state;
private $zip;
private $phone;
private $products;

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

public function setId($id) {
$this->id = $id;
}

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

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

public function getAddress() {
return $this->address;
}

public function setAddress($address) {
$this->address = $address;
}

public function getCity() {
return $this->city;
}

public function setCity($city) {
$this->city = $city;
}

public function getState() {
return $this->state;
}

public function setState($state) {
$this->state = $state;
}

public function getZip() {
return $this->zip;
}

public function setZip($zip) {
$this->zip = $zip;
}

public function getPhone() {
return $this->phone;
}

public function setPhone($phone) {
$this->phone = $phone;
}

public function getProducts() {
return $this->products;
}

public function setProducts($products) {
$this->products = $products;
}

public function addProduct(ProductBO $product) {
// Verify that product does not already exist in the list
if (!empty($this->products)) {
if (in_array($product, $this->products)) {
trigger_error(printf("%s already exists in the product list", $product->getDescription()), E_USER_ERROR);
}
}

//if (!empty($this->products) && in_array($product, $this->products)) {
// trigger_error(printf("%s already exists in the product list", $product->getDescription()), E_USER_ERROR);
//}

// Add product to list
$this->products[] = $product;

// Set vendor inside product BO
$product->setVendor($this);
}

public function removeProduct(ProductBO $product) {
// Verify that product already exists in the list
if (empty($this->products) || !in_array($product, $this->products, true)) {
trigger_error(printf("%s does not exist in the product list", $product->getDescription()), E_USER_ERROR);
}

// Remove product from the list
unset($this->products[array_search($product, $this->products, true)]);

// Re-index products list
$this->products = array_merge($this->products);

// Remove vendor from product BO
$product->setVendor(null);
}

public function save() {
VendorDAO::saveVendor($this);
}
}
?>

/PHPSandbox/Includes/DAO/ProductDAO.php

<?
require_once($_SERVER['DOCUMENT_ROOT'] . "/PHPSandbox/Includes/DBConn.php");
require_once($_SERVER['DOCUMENT_ROOT'] . "/PHPSandbox/Includes/BO/ProductBO.php");
require_once($_SERVER['DOCUMENT_ROOT'] . "/PHPSandbox/Includes/DAO/VendorDAO.php");

class ProductDAO {

public static function saveProduct(ProductBO &$product) {
if ($product->getId()) {
self::updateProduct($product);
} else {
self::insertProduct($product);
}
}

private static function updateProduct(ProductBO $product) {
if (!$product->getVendor()) {
trigger_error("Product does not have a vendor", E_USER_ERROR);
}

if (!$product->getVendor()->getId()) {
trigger_error("The vendor of this product must be saved before saving the product.", E_USER_ERROR);
}

$mysqli = DBConn::getConn();

$stmt = $mysqli->prepare("UPDATE product SET sku=?, description=?, cost=?, vendorId=? WHERE id=?")
or die(trigger_error(printf("Mysqli error: %s", $mysqli->error), E_USER_ERROR));

$stmt->bind_param(
'ssdii',
$product->getSku(),
$product->getDescription(),
$product->getCost(),
$product->getVendor()->getId(),
$product->getId());

$stmt->execute();
}

private static function insertProduct(ProductBO &$product) {
if (!$product->getVendor()) {
trigger_error("Product does not have a vendor", E_USER_ERROR);
}

if (!$product->getVendor()->getId()) {
trigger_error("The vendor of this product must be saved before saving the product.", E_USER_ERROR);
}

$mysqli = DBConn::getConn();

$stmt = $mysqli->prepare("INSERT INTO product VALUES (null, ?, ?, ?, ?)")
or die(trigger_error(printf("Mysqli error: %s", $mysqli->error), E_USER_ERROR));

$stmt->bind_param(
'ssdi',
$product->getSku(),
$product->getDescription(),
$product->getCost(),
$product->getVendor()->getId());

$stmt->execute();

$product->setId($mysqli->insert_id);
}

public static function getProductById($id) {
$mysqli = DBConn::getConn();

$stmt = $mysqli->prepare("SELECT * FROM product WHERE id = ?")
or die(trigger_error(printf("Mysqli error: %s", $mysqli->error), E_USER_ERROR));

$stmt->bind_param(
'i',
$id);

$stmt->execute();

$stmt->bind_result(
$id,
$sku,
$description,
$cost,
$vendorId);

if ($stmt->fetch()) {
$product = new ProductBO();
$product->setId($id);
$product->setSku($sku);
$product->setDescription($description);
$product->setCost($cost);
$product->setVendor(VendorDAO::getVendorById($vendorId));
return $product;
} else {
return false;
}
}

public static function getProductsForVendor(VendorBO &$vendor) {
$mysqli = DBConn::getConn();

$stmt = $mysqli->prepare("SELECT * FROM product WHERE vendorId = ?")
or die(trigger_error(printf("Mysqli error: %s", $mysqli->error), E_USER_ERROR));

$stmt->bind_param(
'i',
$vendor->getId());

$stmt->execute();

$stmt->bind_result(
$id,
$sku,
$description,
$cost,
$vendorId);

$productList = array();

while ($stmt->fetch()) {
$product = new ProductBO();
$product->setId($id);
$product->setSku($sku);
$product->setDescription($description);
$product->setCost($cost);
$product->setVendor($vendor);
$productList[] = $product;
}

return $productList;
}

public static function deleteProduct(ProductBO &$product) {
// If the product is assigned to a vendor, remove it from the vendor's
// product list just to keep our vendor BO clean
if ($product->getVendor()) {
$product->getVendor()->removeProduct($product);
}

$mysqli = DBConn::getConn();

$stmt = $mysqli->prepare("DELETE FROM product WHERE id = ?")
or die(trigger_error(printf("Mysqli error: %s", $mysqli->error), E_USER_ERROR));

$stmt->bind_param(
'i',
$product->getId());

$stmt->execute();
}

}
?>

/PHPSandbox/Includes/DAO/VendorDAO.php

<?
require_once($_SERVER['DOCUMENT_ROOT'] . "/PHPSandbox/Includes/DBConn.php");
require_once($_SERVER['DOCUMENT_ROOT'] . "/PHPSandbox/Includes/BO/VendorBO.php");
require_once($_SERVER['DOCUMENT_ROOT'] . "/PHPSandbox/Includes/DAO/ProductDAO.php");

class VendorDAO {

public static function saveVendor(VendorBO &$vendor) {
if ($vendor->getId()) {
self::updateVendor($vendor);
} else {
self::insertVendor($vendor);
}

if ($vendor->getProducts()) {
foreach ($vendor->getProducts() as $product) {
ProductDAO::saveProduct($product);
}
}
}

private static function updateVendor(VendorBO $vendor) {
$mysqli = DBConn::getConn();

$stmt = $mysqli->prepare("
UPDATE vendor SET
name=?,
address=?,
city=?,
state=?,
zip=?,
phone=?
WHERE
id=?")
or die(trigger_error(printf("Mysqli error: %s", $mysqli->error), E_USER_ERROR));

$stmt->bind_param(
'ssssssi',
$vendor->getName(),
$vendor->getAddress(),
$vendor->getCity(),
$vendor->getState(),
$vendor->getZip(),
$vendor->getPhone(),
$vendor->getId());

$stmt->execute();
}

private static function insertVendor(VendorBO &$vendor) {
$mysqli = DBConn::getConn();

$stmt = $mysqli->prepare("INSERT INTO vendor VALUES (null, ?, ?, ?, ?, ?, ?)")
or die(trigger_error(printf("Mysqli error: %s", $mysqli->error), E_USER_ERROR));

$stmt->bind_param(
'ssssss',
$vendor->getName(),
$vendor->getAddress(),
$vendor->getCity(),
$vendor->getState(),
$vendor->getZip(),
$vendor->getPhone());

$stmt->execute();

$vendor->setId($mysqli->insert_id);
}

public static function getVendorById($id) {
$mysqli = DBConn::getConn();

$stmt = $mysqli->prepare("SELECT * FROM vendor WHERE id = ?")
or die(trigger_error(printf("Mysqli error: %s", $mysqli->error), E_USER_ERROR));

$stmt->bind_param(
'i',
$id);

$stmt->execute();

$stmt->bind_result(
$id,
$name,
$address,
$city,
$state,
$zip,
$phone);

if ($stmt->fetch()) {
$vendor = new VendorBO();
$vendor->setId($id);
$vendor->setName($name);
$vendor->setAddress($address);
$vendor->setCity($city);
$vendor->setState($state);
$vendor->setZip($zip);
$vendor->setPhone($phone);
$vendor->setProducts(ProductDAO::getProductsForVendor($vendor));
return $vendor;
} else {
return false;
}
}

public static function deleteVendor(VendorBO $vendor) {
// If the product is assigned to the vendor, delete it
if ($vendor->getProducts()) {
foreach ($vendor->getProducts() as $product) {
ProductDAO::deleteProduct($product);
}
}

$mysqli = DBConn::getConn();

$stmt = $mysqli->prepare("DELETE FROM product WHERE id = ?")
or die(trigger_error(printf("Mysqli error: %s", $mysqli->error), E_USER_ERROR));

$stmt->bind_param(
'i',
$product->getId());

$stmt->execute();
}

}
?>

/PHPSandbox/Includes/StrictClass.php

<?
abstract class StrictClass {

public function __get($var_name) {
trigger_error("Class '" . get_class($this) . "' does not have member '$var_name' publicly available", E_USER_ERROR);
}

public function __set($var_name, $var_value) {
trigger_error("Class '" . get_class($this) . "' does not have member '$var_name' publicly available", E_USER_ERROR);
}

}
?>

/PHPSandbox/Includes/DBConn.php

<?
Class DBConn {

static private $host = '***YOUR MYSQL HOST***';
private static $username = '***YOUR USERNAME***';
private static $password = '***YOUR PASSWORD***';
private static $db = '***YOUR DATABASE NAME***';

// Prevent class instantiation
private function __construct() {
trigger_error('Instantiation is not allowed.', E_USER_ERROR);
}

// Return a new connection
// Note that connection pooling is not a feature of mysqli
// If you get a gabajillion hits to your site per minute, it may
// be worth implementing your own connection pool, but
// this will work for the majority of cases
public static function getConn() {
$mysqli = new mysqli(self::$host, self::$username, self::$password, self::$db);

if (mysqli_connect_errno()) {
trigger_error(printf("Mysqli error: %s", mysqli_connect_error()), E_USER_ERROR);
}

return $mysqli;
}

// Prevent class cloning
public function __clone()
{
trigger_error('Clone is not allowed.', E_USER_ERROR);
}
}

?>

Once we have our core files set up, we can now test them out with a couple functions I’ve set up. Note that these are strictly for testing, but they can give you an idea of how the different classes and objects interoperate and how you could go about programming a full application using object-oriented PHP.

/PHPSandbox/TestFunctions.php

<?
require_once($_SERVER['DOCUMENT_ROOT'] . "/PHPSandbox/Includes/BO/ProductBO.php");
require_once($_SERVER['DOCUMENT_ROOT'] . "/PHPSandbox/Includes/DAO/ProductDAO.php");
require_once($_SERVER['DOCUMENT_ROOT'] . "/PHPSandbox/Includes/BO/VendorBO.php");
require_once($_SERVER['DOCUMENT_ROOT'] . "/PHPSandbox/Includes/DAO/VendorDAO.php");

function createNewVendorWithNewProducts() {
$brinkton = new VendorBO();
$brinkton->setName("Brinkton Manufacturing");
$brinkton->setAddress("100 N 100 S");
$brinkton->setCity("Provo");
$brinkton->setState("UT");
$brinkton->setZip("84606");
$brinkton->setPhone("801-555-5555");

$chair = new ProductBO();
$chair->setSku("KAL-12KDUFLKDU134");
$chair->setDescription("Blackstone Chair");
$chair->setCost(119.45);
$brinkton->addProduct($chair);

$desk = new ProductBO();
$desk->setSku("JKD-14LKJDFIUL183");
$desk->setDescription("Swingline Table");
$desk->setCost(212.98);
$brinkton->addProduct($desk);

// This will cascade and save all the products
$brinkton->save();
}

function removeExistingProductFromExistingVendor() {
$brinkton = VendorDAO::getVendorById(3);
$products = $brinkton->getProducts();
$productToRemove = $products[0];

// This will both remove the product from the database
// and remove it from the vender BO that is loaded
ProductDAO::deleteProduct($productToRemove);
}
?>

That’s it! That’s all I’ve got! Thanks for reading.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.