Wednesday, April 13, 2011

[TUT] OOP Advance: All About the 'Static' Keyword

All About the 'Static' Keyword

By: Burak Guzel

http://net.tutsplus.com/articles/news/new-premium-tutorial-all-about-the-%E2%80%9Cstatic%E2%80%9D-keyword

Today we are going to learn how to use the 'static' keyword in PHP. We are going go over the basics and build some examples. It is time to add this interesting language feature to your web development bag of tricks.

Tutorial Details
  • Language: PHP
  • Version: 5+
  • Difficulty: Intermediate
  • Estimated Completion Time: 1 hour

1 A Quick Example

Let's stay with a quick example first, to see the effect of using the static keyword.
Imagine writing a function that remembers how many times total it has been called. If you are not familiar with the 'static' keyword, you may resort to using some global variable:
  1. $call_me_count = 0;  
  2.   
  3. function call_me() {  
  4.   
  5.     global $call_me_count;  
  6.   
  7.     $call_me_count++;  
  8.   
  9.     echo "You called me $call_me_count times <br>\n";  
  10.   
  11. }  
  12.   
  13. // output => You called me 1 times  
  14. call_me();  
  15.   
  16. // output => You called me 2 times  
  17. call_me();  
  18.   
  19. // output => You called me 3 times  
  20. call_me();  
  21.   
  22. // output => You called me 4 times  
  23. call_me();  
Just because this works does not mean you should write code this way. Generally speaking, global variables are frowned upon. It's not a good idea to have global variables floating around, that some function only utilize privately.
Now enter the 'static' keyword. Same code can be rewritten like this:
  1. function call_me() {  
  2.   
  3.     // a static variable  
  4.     static $call_me_count = 0;  
  5.   
  6.     $call_me_count++;  
  7.   
  8.     echo "You called me $call_me_count times <br>\n";  
  9.   
  10. }  
  11.   
  12. // output => You called me 1 times  
  13. call_me();  
  14.   
  15. // output => You called me 2 times  
  16. call_me();  
  17.   
  18. // output => You called me 3 times  
  19. call_me();  
  20.   
  21. // output => You called me 4 times  
  22. call_me();  
Same result, no more global variables. But how does this work exactly?
Normally, all local variables inside of a function get destroyed once the function returns. The 'static' keywords lets PHP know to keep the variable alive. So, the next time the function is called, the value inside the variable is retained.

2 Valid Initial Values

As seen the previous example, when we assigned the value of 0 to the variable, it actually did not execute an assignment statement. It only set the initial value for the variable. That may sound like the same thing, but there is a slight difference. The variable can only be initialized to a fixed value, and not an expression.
Let's see some examples of valid and invalid initial values. First, let's look at numbers:
  1. function foo() {  
  2.   
  3.     // valid  
  4.     static $a = 9;  
  5.   
  6.     // causes parse error  
  7.     static $a = sqrt(81);  
  8.   
  9.     // valid  
  10.     static $a = 3.14;  
  11.   
  12.     // causes parse error  
  13.     static $a = pi();  
  14.   
  15.     // causes parse error  
  16.     static $a = 1 + 3;  
  17.   
  18. }  
As you can see, not even basic math operations are allowed. All you can assign is fixed numbers.
We can also assign strings:
  1. function foo() {  
  2.   
  3.     // valid  
  4.     static $a = '';  
  5.   
  6.     // valid  
  7.     static $a = 'hello';  
  8.   
  9.     // causes parse error  
  10.     static $a = strtoupper('hello');  
  11.   
  12.     // causes parse error  
  13.     static $a = 'hello' . 'world';  
  14.   
  15. }  
Again, it has to be fixed strings, and not even basic concatenation is allowed.
Booleans, Arrays and Constants will work too:
  1. define("SOME_CONSTANT", 789);  
  2.   
  3. function foo() {  
  4.   
  5.     // valid  
  6.     static $a = array(1,2,3);  
  7.   
  8.     // valid  
  9.     static $a = SOME_CONSTANT;  
  10.   
  11.     // valid  
  12.     static $a = array(1,2,'three',  
  13.         array (  
  14.             'foo' => 'bar'  
  15.         )  
  16.     );  
  17.   
  18.     // valid  
  19.     static $a = true;  
  20.   
  21. }  

3 Building an Alternate() Function

Now that we know how static variables inside functions work, let's build something useful with it.
Here is a simple HTML page with a simple table in it:
  1.     <title>My Table</title>  
  2.     <meta charset="UTF-8">  
  3.   
  4.   
  5.   
  6. <table>  
  7.     <thead>  
  8.         <tr>  
  9.             <th>ID</th>  
  10.             <th>Name</th>  
  11.             <th>Category</th>  
  12.         </tr>  
  13.     </thead>  
  14.   
  15.     <tbody>  
  16.         <tr>  
  17.             <td>1</td>  
  18.             <td>Apple</td>  
  19.             <td>Fruit</td>  
  20.         </tr>  
  21.         <tr>  
  22.             <td>2</td>  
  23.             <td>Carrot</td>  
  24.             <td>Vegetable</td>  
  25.         </tr>  
  26.         <tr>  
  27.             <td>3</td>  
  28.             <td>Dog</td>  
  29.             <td>Animal</td>  
  30.         </tr>  
  31.         <tr>  
  32.             <td>4</td>  
  33.             <td>Oven</td>  
  34.             <td>Appliance</td>  
  35.         </tr>  
  36.     </tbody>  
  37. </table>  
The result is:
Now let's add some css, and make the rows alternate color:
  1.     <title>My Table</title>  
  2.     <meta charset="UTF-8">  
  3.     <style>  
  4.         table {  
  5.             width: 300px;  
  6.         }  
  7.         tr.odd {  
  8.             background-color: #DDD;  
  9.         }  
  10.         tr.even {  
  11.             background-color: #BBB;  
  12.         }  
  13.         body {  
  14.             font-family: Arial;  
  15.         }  
  16.     </style>  
  17.   
  18.   
  19.   
  20. <table>  
  21.     <thead>  
  22.         <tr>  
  23.             <th>ID</th>  
  24.             <th>Name</th>  
  25.             <th>Category</th>  
  26.         </tr>  
  27.     </thead>  
  28.   
  29.     <tbody>  
  30.         <tr class="odd">  
  31.             <td>1</td>  
  32.             <td>Apple</td>  
  33.             <td>Fruit</td>  
  34.         </tr>  
  35.         <tr class="even">  
  36.             <td>2</td>  
  37.             <td>Carrot</td>  
  38.             <td>Vegetable</td>  
  39.         </tr>  
  40.         <tr class="odd">  
  41.             <td>3</td>  
  42.             <td>Dog</td>  
  43.             <td>Animal</td>  
  44.         </tr>  
  45.         <tr class="even">  
  46.             <td>4</td>  
  47.             <td>Oven</td>  
  48.             <td>Appliance</td>  
  49.         </tr>  
  50.     </tbody>  
  51. </table>  
Now it looks like this:
If the data was coming from a database, and the table rows were output within a loop, you would need to add some code to be able to set those alternating CSS classes to each row. Most people would just go ahead and create a variable within that loop, add a conditional or ternary statement to keep alternating its value.
However, we are going to build a more elegant and reusable solution. We are going to create a function named alternate() that utilizes the concept of static variables.
  1. function alternate($a, $b) {  
  2.   
  3.     static $alt = false;  
  4.   
  5.     // reverse the value of alt (true <=> false)  
  6.     $alt = !$alt;  
  7.   
  8.     if ($alt) {  
  9.         return $a; // return first value  
  10.     } else {  
  11.         return $b; // return second value  
  12.     }  
  13.   
  14. }  
  15.   
  16.   
  17. // Example Usage:  
  18.   
  19. // output: odd  
  20. echo alternate('odd', 'even');  
  21.   
  22. // output: even  
  23. echo alternate('odd', 'even');  
  24.   
  25. // output: odd  
  26. echo alternate('odd', 'even');  
We call the function with two arguments. Every time it is called, it returns back one of the arguments in an alternating fashion. It accomplishes this by keeping a boolean value in a static variable called $alt. Every time it flips this variables value, to determine which one of the two arguments to return.
So, let's use that in our page, by putting it all together:
  1. <!--?php  
  2. function alternate($a$b) {  
  3.   
  4.     static $alt = false;  
  5.   
  6.     // reverse the value of alt (true <=--> false)  
  7.     $alt = !$alt;  
  8.   
  9.     if ($alt) {  
  10.         return $a// return first value  
  11.     } else {  
  12.         return $b// return second value  
  13.     }  
  14.   
  15. }  
  16.   
  17.   
  18.   
  19. $rows = array(  
  20.     array(1, 'Apple''Fruit'),  
  21.     array(2, 'Carrot''Vegetable'),  
  22.     array(3, 'Dog''Animal'),  
  23.     array(4, 'Oven''Appliance')  
  24. );  
  25.   
  26. ?>  
  27.   
  28.   
  29.   
  30.     <title>My Table</title>  
  31.     <meta charset="UTF-8">  
  32.     <style>  
  33.         table {  
  34.             width: 300px;  
  35.         }  
  36.         tr.odd {  
  37.             background-color: #DDD;  
  38.         }  
  39.         tr.even {  
  40.             background-color: #BBB;  
  41.         }  
  42.         body {  
  43.             font-family: Arial;  
  44.         }  
  45.     </style>  
  46.   
  47.   
  48. <table>  
  49.   
  50.     <thead>  
  51.         <tr>  
  52.             <th>ID</th>  
  53.             <th>Name</th>  
  54.             <th>Category</th>  
  55.         </tr>  
  56.     </thead>  
  57.     <tbody>  
  58.         <!--?php foreach ($rows as $row): ?-->  
  59.             <tr class="<?php echo alternate('odd','even'); ?>">  
  60.                 <td><!--?php echo implode('</td--></td><td>'$row); ?></td>  
  61.             </tr>  
  62.         <!--?php endforeach; ?-->  
  63.     </tbody>  
  64.   
  65.   
  66. </table>  
And the final result is the same:




4 Improving Our Alternate() Function

The function we created works well, if we use it only in one spot in our scripts. There is a small problem with it, however. If we use it in multiple places, or in nested loops, it may not return the values in the order we intended. Let's look this code to demonstrate this issue:
  1. // a  
  2. echo alternate('a','b') . "<br>\n";  
  3. // b  
  4. echo alternate('a','b') . "<br>\n";  
  5. // a  
  6. echo alternate('a','b') . "<br>\n";  
  7.   
  8.   
  9. // even  
  10. // (the second value is returned first!)  
  11. echo alternate('odd','even') . "<br>\n";  
  12. // odd  
  13. echo alternate('odd','even') . "<br>\n";  
  14.   
  15.   
  16. // bar  
  17. // (same problem)  
  18. echo alternate('foo','bar') . "<br>\n";  
  19.   
  20.   
  21. // a  
  22. // (last time it was 'a' too)  
  23. echo alternate('a','b') . "<br>\n";  
Our function needs to be aware that it is being called from different places, and make sure to return the values accordingly. We can accomplish this by adding an optional last parameter, to assign an ID number. And we pass a unique ID from each different place we call it from, to avoid these conflicts:
  1. function alternate($a$b$id = 0) {  
  2.   
  3.     static $alt = array();  
  4.   
  5.     if (!isset($alt[$id])) {  
  6.         $alt[$id] = false;  
  7.     }  
  8.   
  9.     $alt[$id] = !$alt[$id];  
  10.   
  11.     if ($alt[$id]) {  
  12.         return $a;  
  13.     } else {  
  14.         return $b;  
  15.     }  
  16.   
  17. }  
  18.   
  19.   
  20. // a  
  21. echo alternate('a','b', 1) . "<br>\n";  
  22. // b  
  23. echo alternate('a','b', 1) . "<br>\n";  
  24. // a  
  25. echo alternate('a','b', 1) . "<br>\n";  
  26.   
  27.   
  28. // odd  
  29. echo alternate('odd','even', 2) . "<br>\n";  
  30. // even  
  31. echo alternate('odd','even', 2) . "<br>\n";  
  32.   
  33.   
  34. // foo  
  35. echo alternate('foo','bar', 3) . "<br>\n";  
  36.   
  37.   
  38. // b  
  39. echo alternate('a','b', 1) . "<br>\n";  
This time we are utilizing an array as a static variable. It will carry a unique boolean value for each different ID number that was passed. This allows it to return the values in correct order.

5 Static Class Members

The 'static' keyword is not only used inside functions. It is actually quite common in object oriented programming. There can be static members, and methods. First we are going to look at how static members work.
Here is the syntax:
  1. class Foo {  
  2.   
  3.     // a static member  
  4.     public static $a = 0;  
  5.   
  6.     // a normal member  
  7.     public $b = 0;  
  8.   
  9.   
  10.     public function bar() {  
  11.   
  12.         // accessing the static member  
  13.         self::$a;  
  14.         self::$a++;  
  15.         self::$a = 3;  
  16.   
  17.         // normal member:  
  18.         $this->b;  
  19.         $this->b++;  
  20.         $this->b = 3;  
  21.   
  22.     }  
  23.   
  24.   
  25. }  
  26.   
  27. // accessing the static member  
  28. // from outside  
  29. Foo::$a;  
  30. Foo::$a++;  
  31. Foo::$a = 3;  
  32.   
  33.   
  34. $obj = new Foo();  
  35. // accessing the normal member  
  36. $obj->b;  
  37. $obj->b++;  
  38. $obj->b = 3;  
Note how we used the 'self::' keyword in front of the static variable for accessing it within the class, rather than '$this'. Also, when used in the outer scope, we do not need to create an instance of the object before we can access the static variables. However, normal class members can only be accessed after we created an instance of them.

6 A Class That Counts Itself

Remember our first example where we had a function that kept a count on how many times it was called? Now let's apply the same principle to object oriented programming.
This class will have the ability to count how many times total it has been created:
  1. class Foo {  
  2.   
  3.     // our instance counter  
  4.     public static $counter = 0;  
  5.   
  6.     // to work as an autoincrement ID  
  7.     public $id = 0;  
  8.   
  9.     // the constructor function  
  10.     public function __construct() {  
  11.   
  12.         // increment counter  
  13.         self::$counter++;  
  14.   
  15.         // save the same number  
  16.         // as the id of this object  
  17.         $this->id = self::$counter;  
  18.   
  19.     }  
  20.   
  21.   
  22. }  
  23.   
  24. // output: 0  
  25. echo Foo::$counter . "\n<br>";  
  26.   
  27.   
  28. $a = new Foo();  
  29. // output: 1  
  30. echo Foo::$counter . "\n<br>";  
  31.   
  32.   
  33. $b = new Foo();  
  34. // output: 2  
  35. echo Foo::$counter . "\n<br>";  
  36.   
  37.   
  38. $c = new Foo();  
  39. // output: 3  
  40. echo Foo::$counter . "\n<br>";  
  41.   
  42.   
  43. // output: 2  
  44. echo $b->id;  
Every time a new object is created, the constructor function is called by default. This function contains code for setting the counter and the id number for that instance of the object. So, if an object was created for the third time, that object will have an id of 3, which is specific only to that object. The counter will keep going up as more objects keep getting created.
Note that regular class members exist separately on each object. However, the static members only exist once globally.

7 Static Class Methods

Not just members, but also methods of a Class can be made 'static'.
  1. class Foo {  
  2.   
  3.     // note the static keyword  
  4.     public static function hello() {  
  5.         echo "Hello World";  
  6.     }  
  7.   
  8. }  
And this is how you can call them:
  1. Foo::hello();  
  2. // prints Hello World  
Note how the syntax is similar to accessing static members, by using double colon (::).
Inside of a static method, a class can refer to itself using the 'self' keyword, and access static members this way:
  1. class Foo {  
  2.   
  3.     public static $call_me_count = 0;  
  4.   
  5.     public static function call_me() {  
  6.   
  7.         self::$call_me_count++;  
  8.   
  9.         echo "You called me ".self::$call_me_count." times <br>\n";  
  10.     }  
  11.   
  12. }  
  13. // output => You called me 1 times  
  14. Foo::call_me();  
  15.   
  16. // output => You called me 2 times  
  17. Foo::call_me();  
  18.   
  19. // output => You called me 3 times  
  20. Foo::call_me();  
  21.   
  22. // output => You called me 4 times  
  23. Foo::call_me();  

8 The Singleton Pattern

A 'Singleton' is a class that can have only exist as a single object instance. It also contains a static reference to this instance.
It might become more clear by looking at the code:
  1. class Foo {  
  2.   
  3.     // to contain an instance of Foo  
  4.     private static $instance;  
  5.   
  6.     // making constructor private, so it can't be called from outside  
  7.     private function __construct() {}  
  8.   
  9.   
  10.     // the singleton method  
  11.     public static function getInstance() {  
  12.   
  13.         // if the instance doesn't exist yet, create it  
  14.         if (!isset(self::$instance)) {  
  15.             $c = __CLASS__;  
  16.             self::$instance = new $c;  
  17.         }  
  18.   
  19.         // return the only instance  
  20.         return self::$instance;  
  21.     }  
  22.   
  23.   
  24.     // cloning is not allowed  
  25.     public function __clone() {  
  26.         trigger_error('Cloning is not allowed.', E_USER_ERROR);  
  27.     }  
  28.   
  29. }  
This is the skeleton structure. You can of course add more methods and member, or simply extend the class.
When we call the getInstance() method, two things happen.
  1. $var = Foo::getInstance();  
First, if there is no Foo object, one is created and assigned to Foo::$instance. Then, once there is that object, it is returned, so $var becomes that object. If you call it multiple times, every time you will get the same exact object; a new instance will not be created.
  1. $var = Foo::getInstance();  
  2. $var2 = Foo::getInstance();  
  3.   
  4. // $var and $var2 reference the same exact object  
Since we made the __construct() method private, we can not create new instances of this object.
  1. // this will throw an error  
  2. $var = new Foo();  

9 A Singleton CurrentUser Class

Now it's time to build a more concrete example with the Singleton Pattern.
Imagine that you have a User class with various methods:
  1. class User {  
  2.   
  3.     public $id;  
  4.     public $name;  
  5.     public $email;  
  6.   
  7.   
  8.     public function load_user($id) {  
  9.   
  10.         // fetch from db  
  11.         // ...  
  12.   
  13.     }  
  14.   
  15.     public function update_user($info) {  
  16.   
  17.         // update db with given $info array  
  18.         // ...  
  19.   
  20.     }  
  21.   
  22.     public function comment_count() {  
  23.   
  24.         // calculate the number of comments  
  25.         // ...  
  26.   
  27.     }  
  28.   
  29.   
  30. }  
We have one method for fetching a user by id from the database. So it could be used like this:
  1. $user = new User();  
  2. $user->load_user($user_id);  
  3.   
  4. // now I can see the name, and other things  
  5. echo $user->name;  
  6. echo $user->comment_count();  
Now imagine that once a user is logged in, you store their id in the session. And next time they load a page, you need to look up that id, and create the related $user object for that user again, if you wish to access the members and methods of that object.
But due to variable scope issues, you will either need to make the $user object global, or keep initializing it from different functions/methods within your script. Here is where a singleton class can come in handy:
  1. class CurrentUser extends User {  
  2.   
  3.     private static $instance;  
  4.   
  5.     private function __construct() {  
  6.         // make sure to call parent constructor  
  7.         parent::__construct();  
  8.     }  
  9.   
  10.     public static function getInstance() {  
  11.   
  12.         // initialize  
  13.         if (!isset(self::$instance)) {  
  14.   
  15.             // does session exist?  
  16.             if (!$_SESSION['user_id']) {  
  17.                 return false;  
  18.             }  
  19.   
  20.             $c = __CLASS__;  
  21.             self::$instance = new $c;  
  22.             self::$instance->load_user($_SESSION['user_id']);  
  23.         }  
  24.   
  25.         return self::$instance;  
  26.     }  
  27.   
  28.   
  29.     public function __clone() {  
  30.         trigger_error('Cloning is not allowed.', E_USER_ERROR);  
  31.     }  
  32.   
  33. }  
Now we can access the CurrentUser class from everywhere in our application:
  1. $user = CurrentUser::getInstance();  
  2.   
  3. if (!$user) {  
  4.     echo "You are not logged in!";  
  5. else {  
  6.     echo Welcome back $user->name";  
  7. }  
So, in our code we did not need to worry about dealing with session. We can simply attempt to get the instance of the CurrentUser object and use it just like any User object, as it extends its functionality.

Conclusion

I hope you enjoyed this tutorial and learned from it. See you next time!

No comments:

Post a Comment