Lesson 9 - Static class members in PHP
In the previous exercise, Solved tasks for OOP in PHP lessons 7-8, we've practiced our knowledge from previous lessons.
In the previous tutorial, Solved tasks for OOP in PHP lessons 7-8, we learned all about polymorphism and learned how to set up an autoloader which simplified class importation. In today's lesson, we're going to learn about a very controversial matter, static class members.
WARNING! Today's lesson is all about static members, which can potentially corrupt the object model. The only reason OOP includes them is for special cases. Overall, everything can be written without static members. We must always think carefully before using static members. Generally, I would recommend for you not to use static members ever, unless you are absolutely sure of what you are doing. Like global variables, static members that make you violate good practices. Today, we'll mostly go over certain classes and methods in .NET, that use static members. Think before you act and there will be less evil in the world.
Static (class) properties
You should be familiar with object-oriented programming by now, or at the very least understand that an application is made up of communicating objects. Objects are instances that are based on a class. Each instance is unique and has abilities (methods) and an inner state (properties).
Aside from instance class members, object-oriented programming allows us to declare static class members as well. Those are properties and methods, that belong to the class rather than an instance. Meaning that we would have to call the method from the class, rather than on an instance (instantiation is not needed). We mark these methods with the "static" keyword to be able to call them from anywhere in the program without having to create an instance. Let's try it out on a simple example program.
Math class
Create a file named Math.php in the "classes" folder. Then, add a square() method, just like you normally would:
class Math { public function square($base) { return $base * $base; } }
Let's square a couple of numbers in index.php using the method we just now added. Create an instance of the Math class and call the method on it:
{PHP} require_once('classes/Math.php'); $math = new Math(); echo($math->square(12));
{PHP} class Math { public function square($base) { return $base * $base; } }
The program outputs the number 144, as expected. Imagine you needed to make similar jointed calculations at many different places in many different classes. It would be very uncomfortable to keep having to create new instances or pass a Math class instance at numerous locations. In this specific case, making the square() method static would be relatively reasonable. Modify the Math class so that it looks like this:
class Math { public static function square($base) { return $base * $base; } }
Then, remove the Math class instance in index.php and call the square() method right on the Math class. To access static members/methods, we use double colons (::):
{PHP} require_once('classes/Math.php'); echo(Math::square(12));
{PHP} class Math { public static function square($base) { return $base * $base; } }
Here, we call the method without having to create an instance. In a way, it's global and visible from anywhere and everywhere, which can lead (if used improperly) to highly unreadable code (we will get to that later on in the course). We know that OOP is all about keeping things encapsulated in their respective classes. However, in this case, it's completely fine. We'll talk about good and bad usage of the static modifier later on.
Combining static and instance members
A class does not have to exclusively include members a single kind. In other words, it can contain both static and instance members. Instance members would only be accessible through the instance, and static ones through the class.
Instance numbering
In a way, static members are shared between instances because they belong to the class, which is the only access point for static members. Now, let's get back to our Human class and its respective descendant. Imagine that we needed to number them, each human would have an $id property, where a unique number would be stored. The very first human created would have an $id equal to 1, the second one $id=2, and so on. Now, how would we keep up with the number of humans we've created?
Easy, add a peopleCount static property to the Human class and set it to 0. Then, the class would keep track of the number of humans that have been created. If you think about it, doing so is a very logical thing to do, because the property is shared by all of the instances. We will also have to increase peopleCount by 1 in the Human constructor and assign its value to the $id variable.
Here's Human.php with this minor modification added:
class Human { public $firstName; public $lastName; public $age; private $tiredness = 0; public $id; private static $peopleCount = 0; public function __construct($firstName, $lastName, $age) { $this->firstName = $firstName; $this->lastName = $lastName; $this->age = $age; self::$peopleCount++; $this->id = self::$peopleCount; } public function sleep($time) { $this->tiredness -= $time * 10; if ($this->tiredness < 0) $this->tiredness = 0; } public function run($distance) { if ($this->tiredness + $distance <= 20) $this->tiredness += $distance; else echo("I'm too tired."); } public function greet() { echo('Hi, my name is ' . $this->firstName); } protected function fullName() { return $this->firstName . ' ' . $this->lastName; } public function __toString() { return $this->firstName . ' - ' . $this->id; } }
After examining the code carefully, you'll see that I added two new properties, $id (instance member) and $peopleCount (static member).
Inside the constructor, we increment the $peopleCount variable and we assign its value to the new instance's $id. To access static members from inside of the class, we use the self keyword. It is sort of like $this, but self, as the name suggests, refers to itself (as a class). You may find it confusing that we have to write a dollar sign after the double colon operator, but don't worry, all we're doing is incrementing the value of peopleCount.
The last change I added is in the __toString() method, where it returns an instance's ID to confirm that ID assignment works properly.
Then, we modify the foreach loop in index.php in a way that it prints people instead having them greet. For completeness' sake here is the code, including the object creation section:
{PHP} require_once('classes/Human.php'); require_once('classes/PhpProgrammer.php'); $people = array(); $people[] = new Human('Carl', 'Smith', 30); $people[] = new PhpProgrammer('John', 'Newman', 24, 'Eclipse'); $people[] = new Human('Jack', 'Newman', 50); $people[] = new PhpProgrammer('Thomas', 'Moore', 28, 'NetBeans'); $people[] = new Human('Maria', 'Newman', 32); foreach ($people as $human) { echo($human . '<br />'); }
{PHP} class Human { public $firstName; public $lastName; public $age; private $tiredness = 0; public $id; private static $peopleCount = 0; public function __construct($firstName, $lastName, $age) { $this->firstName = $firstName; $this->lastName = $lastName; $this->age = $age; self::$peopleCount++; $this->id = self::$peopleCount; } public function sleep($time) { $this->tiredness -= $time * 10; if ($this->tiredness < 0) $this->tiredness = 0; } public function run($distance) { if ($this->tiredness + $distance <= 20) $this->tiredness += $distance; else echo("I'm too tired."); } public function greet() { echo('Hi, my name is ' . $this->firstName); } protected function fullName() { return $this->firstName . ' ' . $this->lastName; } public function __toString() { return $this->firstName . ' - ' . $this->id; } }
{PHP} class PhpProgrammer extends Human { public $ide; public function __construct($firstName, $lastName, $age, $ide) { $this->ide = $ide; parent::__construct($firstName, $lastName, $age); } public function greet() { echo("Hello world! I'm $this->firstName"); } public function program() { echo("I'm programming in {$this->ide}..."); } }
Program output:
As you can see, the static $peopleCount property can only be accessed through the Human class. When PHP loads the class for the first time, it creates the property and sets its value to 0. Static properties stay in memory until the script terminates regardless of whether the class has been instantiated. We can access its value through instance methods (in this case, the constructor). However, an instance method cannot be called from a static one, simply because an instance is required in order to call instance methods.
To get this example working without using static members, we'd have to use an object that creates Human instances. Objects that create other objects are referred to as factories, which you can look up and read about if you want.
Static members are very important, widely used and commonly misunderstood programming techniques. For this reason, we will dedicate two more lessons to them. We will talk about scenarios in which it would be reasonable to use static members, as well as all of the others where it isn't. You'll run into them all in the next lesson, Static class members in PHP pt. 2 - constants, so you absolutely need to understand how they work. We will continue with this next time, and go over how to use constants in PHP. Today's sample code can be downloaded in the attachment below.
Did you have a problem with anything? Download the sample application below and compare it with your project, you will find the error easily.
Download
By downloading the following file, you agree to the license terms
Downloaded 31x (3.24 kB)
Application includes source codes in language PHP