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.
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
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:
- $call_me_count = 0;
- function call_me() {
- global $call_me_count;
- $call_me_count++;
- echo "You called me $call_me_count times <br>\n";
- }
- // output => You called me 1 times
- call_me();
- // output => You called me 2 times
- call_me();
- // output => You called me 3 times
- call_me();
- // output => You called me 4 times
- 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:
- function call_me() {
- // a static variable
- static $call_me_count = 0;
- $call_me_count++;
- echo "You called me $call_me_count times <br>\n";
- }
- // output => You called me 1 times
- call_me();
- // output => You called me 2 times
- call_me();
- // output => You called me 3 times
- call_me();
- // output => You called me 4 times
- 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:
- function foo() {
- // valid
- static $a = 9;
- // causes parse error
- static $a = sqrt(81);
- // valid
- static $a = 3.14;
- // causes parse error
- static $a = pi();
- // causes parse error
- static $a = 1 + 3;
- }
As you can see, not even basic math operations are allowed. All you can assign is fixed numbers.
We can also assign strings:
- function foo() {
- // valid
- static $a = '';
- // valid
- static $a = 'hello';
- // causes parse error
- static $a = strtoupper('hello');
- // causes parse error
- static $a = 'hello' . 'world';
- }
Again, it has to be fixed strings, and not even basic concatenation is allowed.
Booleans, Arrays and Constants will work too:
- define("SOME_CONSTANT", 789);
- function foo() {
- // valid
- static $a = array(1,2,3);
- // valid
- static $a = SOME_CONSTANT;
- // valid
- static $a = array(1,2,'three',
- array (
- 'foo' => 'bar'
- )
- );
- // valid
- static $a = true;
- }
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:
- <title>My Table</title>
- <meta charset="UTF-8">
- <table>
- <thead>
- <tr>
- <th>ID</th>
- <th>Name</th>
- <th>Category</th>
- </tr>
- </thead>
- <tbody>
- <tr>
- <td>1</td>
- <td>Apple</td>
- <td>Fruit</td>
- </tr>
- <tr>
- <td>2</td>
- <td>Carrot</td>
- <td>Vegetable</td>
- </tr>
- <tr>
- <td>3</td>
- <td>Dog</td>
- <td>Animal</td>
- </tr>
- <tr>
- <td>4</td>
- <td>Oven</td>
- <td>Appliance</td>
- </tr>
- </tbody>
- </table>
The result is:
Now let's add some css, and make the rows alternate color:
- <title>My Table</title>
- <meta charset="UTF-8">
- <style>
- table {
- width: 300px;
- }
- tr.odd {
- background-color: #DDD;
- }
- tr.even {
- background-color: #BBB;
- }
- body {
- font-family: Arial;
- }
- </style>
- <table>
- <thead>
- <tr>
- <th>ID</th>
- <th>Name</th>
- <th>Category</th>
- </tr>
- </thead>
- <tbody>
- <tr class="odd">
- <td>1</td>
- <td>Apple</td>
- <td>Fruit</td>
- </tr>
- <tr class="even">
- <td>2</td>
- <td>Carrot</td>
- <td>Vegetable</td>
- </tr>
- <tr class="odd">
- <td>3</td>
- <td>Dog</td>
- <td>Animal</td>
- </tr>
- <tr class="even">
- <td>4</td>
- <td>Oven</td>
- <td>Appliance</td>
- </tr>
- </tbody>
- </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.
- function alternate($a, $b) {
- static $alt = false;
- // reverse the value of alt (true <=> false)
- $alt = !$alt;
- if ($alt) {
- return $a; // return first value
- } else {
- return $b; // return second value
- }
- }
- // Example Usage:
- // output: odd
- echo alternate('odd', 'even');
- // output: even
- echo alternate('odd', 'even');
- // output: odd
- 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:
- <!--?php
- function alternate($a, $b) {
- static $alt = false;
- // reverse the value of alt (true <=--> false)
- $alt = !$alt;
- if ($alt) {
- return $a; // return first value
- } else {
- return $b; // return second value
- }
- }
- $rows = array(
- array(1, 'Apple', 'Fruit'),
- array(2, 'Carrot', 'Vegetable'),
- array(3, 'Dog', 'Animal'),
- array(4, 'Oven', 'Appliance')
- );
- ?>
- <title>My Table</title>
- <meta charset="UTF-8">
- <style>
- table {
- width: 300px;
- }
- tr.odd {
- background-color: #DDD;
- }
- tr.even {
- background-color: #BBB;
- }
- body {
- font-family: Arial;
- }
- </style>
- <table>
- <thead>
- <tr>
- <th>ID</th>
- <th>Name</th>
- <th>Category</th>
- </tr>
- </thead>
- <tbody>
- <!--?php foreach ($rows as $row): ?-->
- <tr class="<?php echo alternate('odd','even'); ?>">
- <td><!--?php echo implode('</td--></td><td>', $row); ?></td>
- </tr>
- <!--?php endforeach; ?-->
- </tbody>
- </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:
- // a
- echo alternate('a','b') . "<br>\n";
- // b
- echo alternate('a','b') . "<br>\n";
- // a
- echo alternate('a','b') . "<br>\n";
- // even
- // (the second value is returned first!)
- echo alternate('odd','even') . "<br>\n";
- // odd
- echo alternate('odd','even') . "<br>\n";
- // bar
- // (same problem)
- echo alternate('foo','bar') . "<br>\n";
- // a
- // (last time it was 'a' too)
- 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:
- function alternate($a, $b, $id = 0) {
- static $alt = array();
- if (!isset($alt[$id])) {
- $alt[$id] = false;
- }
- $alt[$id] = !$alt[$id];
- if ($alt[$id]) {
- return $a;
- } else {
- return $b;
- }
- }
- // a
- echo alternate('a','b', 1) . "<br>\n";
- // b
- echo alternate('a','b', 1) . "<br>\n";
- // a
- echo alternate('a','b', 1) . "<br>\n";
- // odd
- echo alternate('odd','even', 2) . "<br>\n";
- // even
- echo alternate('odd','even', 2) . "<br>\n";
- // foo
- echo alternate('foo','bar', 3) . "<br>\n";
- // b
- 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:
- class Foo {
- // a static member
- public static $a = 0;
- // a normal member
- public $b = 0;
- public function bar() {
- // accessing the static member
- self::$a;
- self::$a++;
- self::$a = 3;
- // normal member:
- $this->b;
- $this->b++;
- $this->b = 3;
- }
- }
- // accessing the static member
- // from outside
- Foo::$a;
- Foo::$a++;
- Foo::$a = 3;
- $obj = new Foo();
- // accessing the normal member
- $obj->b;
- $obj->b++;
- $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:
- class Foo {
- // our instance counter
- public static $counter = 0;
- // to work as an autoincrement ID
- public $id = 0;
- // the constructor function
- public function __construct() {
- // increment counter
- self::$counter++;
- // save the same number
- // as the id of this object
- $this->id = self::$counter;
- }
- }
- // output: 0
- echo Foo::$counter . "\n<br>";
- $a = new Foo();
- // output: 1
- echo Foo::$counter . "\n<br>";
- $b = new Foo();
- // output: 2
- echo Foo::$counter . "\n<br>";
- $c = new Foo();
- // output: 3
- echo Foo::$counter . "\n<br>";
- // output: 2
- 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'.
- class Foo {
- // note the static keyword
- public static function hello() {
- echo "Hello World";
- }
- }
And this is how you can call them:
- Foo::hello();
- // 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:
- class Foo {
- public static $call_me_count = 0;
- public static function call_me() {
- self::$call_me_count++;
- echo "You called me ".self::$call_me_count." times <br>\n";
- }
- }
- // output => You called me 1 times
- Foo::call_me();
- // output => You called me 2 times
- Foo::call_me();
- // output => You called me 3 times
- Foo::call_me();
- // output => You called me 4 times
- 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:
- class Foo {
- // to contain an instance of Foo
- private static $instance;
- // making constructor private, so it can't be called from outside
- private function __construct() {}
- // the singleton method
- public static function getInstance() {
- // if the instance doesn't exist yet, create it
- if (!isset(self::$instance)) {
- $c = __CLASS__;
- self::$instance = new $c;
- }
- // return the only instance
- return self::$instance;
- }
- // cloning is not allowed
- public function __clone() {
- trigger_error('Cloning is not allowed.', E_USER_ERROR);
- }
- }
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.
- $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.
- $var = Foo::getInstance();
- $var2 = Foo::getInstance();
- // $var and $var2 reference the same exact object
Since we made the __construct() method private, we can not create new instances of this object.
- // this will throw an error
- $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:
- class User {
- public $id;
- public $name;
- public $email;
- public function load_user($id) {
- // fetch from db
- // ...
- }
- public function update_user($info) {
- // update db with given $info array
- // ...
- }
- public function comment_count() {
- // calculate the number of comments
- // ...
- }
- }
We have one method for fetching a user by id from the database. So it could be used like this:
- $user = new User();
- $user->load_user($user_id);
- // now I can see the name, and other things
- echo $user->name;
- 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:
- class CurrentUser extends User {
- private static $instance;
- private function __construct() {
- // make sure to call parent constructor
- parent::__construct();
- }
- public static function getInstance() {
- // initialize
- if (!isset(self::$instance)) {
- // does session exist?
- if (!$_SESSION['user_id']) {
- return false;
- }
- $c = __CLASS__;
- self::$instance = new $c;
- self::$instance->load_user($_SESSION['user_id']);
- }
- return self::$instance;
- }
- public function __clone() {
- trigger_error('Cloning is not allowed.', E_USER_ERROR);
- }
- }
Now we can access the CurrentUser class from everywhere in our application:
- $user = CurrentUser::getInstance();
- if (!$user) {
- echo "You are not logged in!";
- } else {
- echo Welcome back $user->name";
- }
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