Статьи

Все о ключевом слове «Static»

Сегодня мы собираемся узнать, как использовать ключевое слово «static» в PHP. Мы собираемся пройтись по основам и построить несколько примеров. Пришло время добавить эту интересную языковую функцию в пакет трюков для веб-разработки.


Давайте сначала остановимся на кратком примере, чтобы увидеть эффект использования статического ключевого слова.

Представьте себе, что вы пишете функцию, которая запоминает, сколько раз она была вызвана. Если вы не знакомы с ключевым словом «static», вы можете прибегнуть к использованию некоторой глобальной переменной:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$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();

То, что это работает, не означает, что вы должны писать код таким образом. Вообще говоря, глобальные переменные осуждаются. Это не очень хорошая идея, чтобы глобальные переменные плавали вокруг, что некоторые функции используют только в частном порядке.

Теперь введите ключевое слово static. Тот же код можно переписать так:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
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();

Тот же результат, больше нет глобальных переменных. Но как это работает точно?

Обычно все локальные переменные внутри функции уничтожаются, как только функция возвращается. «Статические» ключевые слова позволяют PHP знать, что переменная остается в живых. Таким образом, при следующем вызове функции значение в переменной сохраняется.


Как видно из предыдущего примера, когда мы присвоили значение 0 переменной, она фактически не выполняла оператор присваивания. Он только устанавливает начальное значение для переменной. Это может звучать как одно и то же, но есть небольшая разница. Переменная может быть инициализирована только фиксированным значением, а не выражением.

Давайте посмотрим несколько примеров действительных и недействительных начальных значений. Сначала давайте посмотрим на цифры:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
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;
 
}

Как видите, не разрешены даже основные математические операции. Все, что вы можете назначить, это фиксированные номера.

Мы также можем назначить строки:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
function foo() {
 
    // valid
    static $a = »;
 
    // valid
    static $a = ‘hello’;
 
    // causes parse error
    static $a = strtoupper(‘hello’);
 
    // causes parse error
    static $a = ‘hello’ .
 
}

Опять же, это должны быть фиксированные строки, и даже базовая конкатенация не допускается.

Булевы, Массивы и Константы тоже будут работать:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
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;
 
}

Теперь, когда мы знаем, как работают статические переменные внутри функций, давайте создадим что-то полезное с ним.

Вот простая HTML-страница с простой таблицей в ней:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<!DOCTYPE HTML>
<html lang=»en-US»>
<head>
    <title>My Table</title>
    <meta charset=»UTF-8″>
</head>
<body>
 
<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>
 
</body>
</html>

Результат:


Теперь давайте добавим немного CSS и сделаем строки чередующимся цветом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<!DOCTYPE HTML>
<html lang=»en-US»>
<head>
    <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>
</head>
<body>
 
<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>
 
</body>
</html>

Теперь это выглядит так:


Если данные поступают из базы данных, а строки таблицы выводятся в цикле, вам необходимо добавить некоторый код, чтобы можно было устанавливать эти чередующиеся классы CSS для каждой строки. Большинство людей просто идут вперед и создают переменную в этом цикле, добавляют условный или троичный оператор, чтобы продолжать чередовать его значение.

Однако мы собираемся создать более элегантное и многоразовое решение. Мы собираемся создать функцию под названием alternate (), которая использует концепцию статических переменных.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function alternate($a, $b) {
 
    static $alt = false;
 
    // reverse the value of alt (true <=> false)
    $alt = !$alt;
 
    if ($alt) {
        return $a;
    } else {
        return $b;
    }
 
}
 
 
// Example Usage:
 
// output: odd
echo alternate(‘odd’, ‘even’);
 
// output: even
echo alternate(‘odd’, ‘even’);
 
// output: odd
echo alternate(‘odd’, ‘even’);

Мы вызываем функцию с двумя аргументами. Каждый раз, когда он вызывается, он возвращает один из аргументов поочередно. Это достигается путем сохранения логического значения в статической переменной с именем $ alt. Каждый раз, когда он переворачивает значение этой переменной, чтобы определить, какой из двух аргументов нужно вернуть.

Итак, давайте использовать это на нашей странице, собрав все вместе:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<?php
function alternate($a, $b) {
 
    static $alt = false;
 
    // reverse the value of alt (true <=> false)
    $alt = !$alt;
 
    if ($alt) {
        return $a;
    } else {
        return $b;
    }
 
}
 
 
 
$rows = array(
    array(1, ‘Apple’, ‘Fruit’),
    array(2, ‘Carrot’, ‘Vegetable’),
    array(3, ‘Dog’, ‘Animal’),
    array(4, ‘Oven’, ‘Appliance’)
);
 
?>
<!DOCTYPE HTML>
<html lang=»en-US»>
<head>
    <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>
</head>
<body>
<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>’, $row);
            </tr>
        <?php endforeach;
    </tbody>
 
 
</table>
</body>
</html>

И конечный результат тот же:



Созданная нами функция работает хорошо, если мы используем ее только в одном месте в наших скриптах. Однако есть небольшая проблема. Если мы используем его в нескольких местах или во вложенных циклах, он может не возвращать значения в том порядке, который мы намеревались. Давайте посмотрим этот код, чтобы продемонстрировать эту проблему:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// a
echo alternate(‘a’,’b’) .
// b
echo alternate(‘a’,’b’) .
// a
echo alternate(‘a’,’b’) .
 
 
// even
// (the second value is returned first!)
echo alternate(‘odd’,’even’) .
// odd
echo alternate(‘odd’,’even’) .
 
 
// bar
// (same problem)
echo alternate(‘foo’,’bar’) .
 
 
// a
// (last time it was ‘a’ too)
echo alternate(‘a’,’b’) .

Наша функция должна знать, что она вызывается из разных мест, и обязательно возвращать значения соответственно. Мы можем сделать это, добавив необязательный последний параметр, чтобы назначить идентификационный номер. И мы передаем уникальный идентификатор из каждого места, откуда мы его вызываем, чтобы избежать этих конфликтов:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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) .
// b
echo alternate(‘a’,’b’, 1) .
// a
echo alternate(‘a’,’b’, 1) .
 
 
// odd
echo alternate(‘odd’,’even’, 2) .
// even
echo alternate(‘odd’,’even’, 2) .
 
 
// foo
echo alternate(‘foo’,’bar’, 3) .
 
 
// b
echo alternate(‘a’,’b’, 1) .

На этот раз мы используем массив в качестве статической переменной. Он будет содержать уникальное логическое значение для каждого другого переданного идентификационного номера. Это позволяет ему возвращать значения в правильном порядке.


Ключевое слово «static» используется не только внутри функций. На самом деле это довольно часто встречается в объектно-ориентированном программировании. Могут быть статические члены и методы. Сначала мы рассмотрим, как работают статические члены.

Вот синтаксис:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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;

Обратите внимание, как мы использовали ключевое слово «self ::» перед статической переменной для доступа к нему внутри класса, а не «$ this». Кроме того, при использовании во внешней области нам не нужно создавать экземпляр объекта, прежде чем мы сможем получить доступ к статическим переменным. Однако к обычным членам класса можно получить доступ только после того, как мы создали их экземпляр.


Помните наш первый пример, где у нас была функция, которая подсчитывала, сколько раз она была вызвана? Теперь давайте применим тот же принцип к объектно-ориентированному программированию.

Этот класс будет иметь возможность подсчитать, сколько раз он был создан:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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 .
 
 
$a = new Foo();
// output: 1
echo Foo::$counter .
 
 
$b = new Foo();
// output: 2
echo Foo::$counter .
 
 
$c = new Foo();
// output: 3
echo Foo::$counter .
 
 
// output: 2
echo $b->id;

Каждый раз, когда создается новый объект, функция конструктора вызывается по умолчанию. Эта функция содержит код для установки счетчика и идентификационный номер для этого экземпляра объекта. Таким образом, если объект был создан в третий раз, этот объект будет иметь идентификатор 3, который относится только к этому объекту. Счетчик будет продолжать расти, так как все больше объектов продолжают создаваться.

Обратите внимание, что обычные члены класса существуют отдельно для каждого объекта. Однако статические члены существуют только один раз в глобальном масштабе.


Не только члены, но и методы класса могут быть сделаны «статическими».

1
2
3
4
5
6
7
8
class Foo {
 
    // note the static keyword
    public static function hello() {
        echo «Hello World»;
    }
 
}

И вот как вы можете назвать их:

1
2
Foo::hello();
// prints Hello World

Обратите внимание, что синтаксис похож на доступ к статическим членам, используя двойное двоеточие (: :).

Внутри статического метода класс может ссылаться на себя с помощью ключевого слова self и получать доступ к статическим членам следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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();

Singleton — это класс, который может существовать только как один экземпляр объекта. Он также содержит статическую ссылку на этот экземпляр.

Это может стать более понятным, если взглянуть на код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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);
    }
 
}

Это структура скелета. Конечно, вы можете добавить больше методов и членов или просто расширить класс.

Когда мы вызываем метод getInstance (), происходят две вещи.

1
$var = Foo::getInstance();

Во-первых, если объекта Foo нет, он создается и присваивается Foo :: $ instance. Затем, когда есть этот объект, он возвращается, и $ var становится этим объектом. Если вы вызываете его несколько раз, каждый раз вы получаете один и тот же точный объект; новый экземпляр не будет создан.

1
2
3
4
$var = Foo::getInstance();
$var2 = Foo::getInstance();
 
// $var and $var2 reference the same exact object

Поскольку мы сделали метод __construct () закрытым, мы не можем создавать новые экземпляры этого объекта.

1
2
// this will throw an error
$var = new Foo();

Теперь пришло время построить более конкретный пример с помощью шаблона Singleton.

Представьте, что у вас есть класс User с различными методами:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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
        // …
 
    }
 
 
}

У нас есть один метод для извлечения пользователя по идентификатору из базы данных. Так что это можно использовать так:

1
2
3
4
5
6
$user = new User();
$user->load_user($user_id);
 
// now I can see the name, and other things
echo $user->name;
echo $user->comment_count();

Теперь представьте, что, как только пользователь вошел в систему, вы сохраняете его идентификатор в сеансе. И в следующий раз, когда они загрузят страницу, вам нужно найти этот идентификатор и снова создать связанный объект $ user для этого пользователя, если вы хотите получить доступ к членам и методам этого объекта.

Но из-за проблем с переменной областью вам нужно либо сделать объект $ user глобальным, либо продолжать инициализировать его из различных функций / методов в вашем скрипте. Вот где может пригодиться одноэлементный класс:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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);
    }
 
}

Теперь мы можем получить доступ к классу CurrentUser из любого места в нашем приложении:

1
2
3
4
5
6
7
$user = CurrentUser::getInstance();
 
if (!$user) {
    echo «You are not logged in!»;
} else {
    echo Welcome back $user->name»;
}

Итак, в нашем коде нам не нужно беспокоиться о работе с сессией. Мы можем просто попытаться получить экземпляр объекта CurrentUser и использовать его точно так же, как любой объект User, поскольку он расширяет его функциональность.


Надеюсь, вам понравился этот урок и выучили его. Увидимся в следующий раз!