Блог


global - зло?

Конечно да! - воскликнет 90% программистов, ведь это (впитывается с молоком матери) написано на всех Хабрах, Мабрах и иже с ними. И приведут кучу обоснований:

1. Сложно понять, где инициализируется переменная, доступ к которой осуществляется посредством global. Соответственно это ухудшает читабельность кода.
2. Глобальная переменная может быть переопределена в дебрях скрипта, и найти это место очень сложно. А значит велик риск сложнодиагностируемых логических ошибок.
3. Код становится сильносвязанным, выполнение одной части будет зависить от другой, где глобальная переменная определена.


Ну и еще кое-что по мелочи. Не стану с этим спорить, все так. Однако посмотрим, что предлагается взамен.

Мы не будем рассматривать обычные переменные, действительно, их лучше не объявлять глобальными. Но есть такие, которые могут потребоваться в любом месте в любое время. Это сродни суперглобальным массивам, таким, как $_POST, $_GET, $_SESSION и так далее. К примеру допустим объект PDO или MYSQLI. То, что может понадобиться в функциях или методах приложения.

Вообще, в качестве отступления, нужно сказать, что в PHP есть механизм для глобальных данных. Это константы. Константы находятся в глобальной области видимости и видны везде. Кроме того, они защищены от переопределения. Константа на то и константа, что в ней нельзя изменить значение. Однако у констант есть один немаловажный изъян - в них нельзя поместить массив или объект.

Есть несколько способов избежать инструкции global, которые считаются кошерными. Посмотрим внимательно.

1. Использовать суперглобальный массив $GLOBALS. Нужно отдать должное - этот способ тоже под опалой у tru-программистов. И впрямь, никаких особых отличий нет, сделать так:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php

    $a 
1;
    $b 2;

    function 
Sum()
    {
         global 
$a$b;

         
$b $a $b;
    } 

    Sum();
    echo 
$b;


или так:

1
2
3
4
5
6
7
8
9
10
11
12
<?php

    $a 
1;
    $b 2;

    function 
Sum()
    {
         
$GLOBALS["b"] = $GLOBALS["a"] + $GLOBALS["b"];
    } 

    Sum();
    echo 
$b;


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

1
2
3
4


    
global $var;

эквивалентна конструкции

1
2
3
4


    $var 
= &$GLOBALS['var'];


2. Чуть более рассово-верным считается использование статического метода какого-нибудь класса, построенного по паттерну singleton

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php


class DB
{
    static 
$link;

    public static function 
connect()
    {
        if(empty(
self::$link))
        {
            
self::$link = @mysqli_connect('localhost''root''''irbis'
                           or die(
'No connect'); 
            
            
mysqli_set_charset(self::$link'utf8');
        }
    }
}

   
DB::connect();
   
   
var_dump(DB::$link);
   


Теперь замысловатую переменную DB::$link, так же как любую глобальную, будет видно везде. Это намного удобнее, так как не нужно в каждой функции и каждом методе  применять инструкцию global или громоздкое и некрасивое обращение к массиву $GLOBALS. Мы кстати в своих уроках используем именно такую конструкцию для организации коннекта.

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php


class DB
{
    static 
$link;

    public static function 
connect()
    {
        if(empty(
self::$link))
        {
            
self::$link = @mysqli_connect('localhost''root''''irbis'
                           or die(
'No connect'); 
            
            
mysqli_set_charset(self::$link'utf8');
        }
    }
}

   
DB::connect();
   
   
var_dump(DB::$link);
// Переопределяем свойство   
   
DB::$link NULL;
   
var_dump(DB::$link);  


А зачит никуда не делся второй недостаток из списка люстраций. 


3. Еще один, более "кошерный" способ - паттерн Registry (Реестр). Сам паттерн достаточно прост, это класс, который регистрирует во внутреннем массиве переменные, которые будут доступны при вызове из объекта. Вот на простом примере:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
 

class Registry 
{
    private 
$arr = array();

    public function 
set($key$value
    {
        if(isset(
$this->arr[$key]))
            return 
false;
     
        
$this->arr[$key] = $value;
        return 
true;
    }

    public function 
get($key
    {
        return 
$this->arr[$key];
    }  

Тут первый метод устанавливает в приватном массиве элемент с заданным ключем и значением, второй позволяет получить значение по ключу. Если элемент с таким ключем уже есть, переопределить его не выйдет:

1
2
3
4
5
6
7
8
9
10
 
    
// Инициализируем объект реестра
    
$registry = new Registry;
    
// Устанавливаем значение сеттером
    
$registry->set('name''Вася Пупкин');
    
// Пытаемся переустановить значение
    
$registry->set('name''Жора Задов');
    
// Получаем значение по ключу, используя get()
    
echo $registry->get('name'); 


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
 
class Registry implements ArrayAccess 
{
    private 
$arr = array();

    public function 
offsetSet($key$value
    {
        if(isset(
$this->arr[$key]))
            return 
false;
     
        
$this->arr[$key] = $value;
        return 
true;
    }

    public function 
offsetGet($key
    {
        return 
$this->arr[$key];
    }   

    public function 
offsetExists($key){}

    public function 
offsetUnset($key){}
}

Обратите внимание, что обязательны все 4 метода, даже если мы не реализуем некоторые из них. Здесь для простоты примера мы ограничились только сеттером и геттером. При таком раскладе можно обращаться к объекту, как к массиву:

1
2
3
4
5
6
7
8
9
 
    $registry 
= new Registry;    
    
// Устанавливаем значение в массив
    
$registry['name'] = 'Вася Пупкин';
    
// Пытаемся переустановить значение
    
$registry['name'] = 'Жора Задов';
    
// Получаем значение, используя доступ как к массиву
    
echo $registry['name'];


Это несколько сокращает код, но довольно ощутимо бьет по прозрачности. Не совсем ясно, откуда этот массив появился.

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

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

Особо "продвинутые" особи пытаются объединить недостатки преимущества обоих паттернов в попытках создать Singleton Registry  (Реестр-одиночка), но оставим это на их совести.

Ну и на практике, Всегда ли стоит игра таких анальных свеч... Вот один из самых ярких пример - функция автолоада. Допустим мы хотим передать в неё массив путей, по которым будут содержаться искомые классы (include_path). Как это сделать?

1. Передать аргументом ничего невозможно.
2. Константой нельзя передать массив.
3. Остается два последних способа - sigletone и registry
4. И самый простой - инструкция global или массив $GLOBALS

Сравните сами по объемам кода и используемым ресурсам.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
 
    $INCLUDE_PATH 
= array('libs',
                          
'classes',
                          
'components'
                          'models'
,
                          
'controllers'
                          
);
     
    function 
__autoload($classname)  
    {
        global 
$INCLUDE_PATH;
     
        foreach (
$INCLUDE_PATH as $include_path)
        {
            
$file $include_path .'/'strtolower($classname) .'.php';
         
            if(
file_exists($file))
            {
                include_once 
$file;
                return;
            }
        }
    }



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

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

Вообще, нужно знать, откуда пошло поверье, что global, это зло и аттавизм. Дело в том, что в древних версиях PHP (до 4 ветки) небыло суперглобальных массивов $_POST, $_GET и иже с ними. Да и с ООП была откровенная беда. Тогда эта инструкция была как нельзя кстати. Теперь такие массивы есть, да и паттернов напридумано куча и маленькая тележка. Кроме того, набирают обороты немспейсы. Вот и поселилась в головах новоявленных программистов паранойя, что global, это исчадие ада и прошлый век. Однако как то умалчивается тот факт, что многие проблемы глобальных переменных вовсе не решаются новыми методами, хотя последние довольно ощутимо усложняют код.

А ведь все намного проще и прозаичнее. Достаточно запомнить одну единственную заповедь:

глобальные переменные только для чтения.

Ведь попытка переопределить элемент суперглобального массива $_POST не повлечет никакой отрицательной реакции интерпретатора, но вызовет бурю негодования в среде программистов - моветон! А в чем различие между штатными суперглобалами и "самодельными" глобальными переменными?

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

К примеру в наших уроках мы используем общую инициализацию переменных из суперглобальных массивов. Это позволяет избежать излишних проверок или нотисов. И используем глобальные массивы $POST и $GET (без подчеркивания). Они в верхнем регистре и изменять их нельзя. Так же, как и их "родителей". Семантически нельзя, технически конечно возможно. Но на то и программист, чтобы писать не просто код, а семантически верный код. Не прячась за фиговыми листочками бестолковых в данном случае паттернов.

Так что использовать global можно и должно. Она совсем не зря имеется в арсенале, и никакое это не зло. Без фанатизма конечно: фанатизм, он плох хоть в ту, хоть в другую сторону. 

Теги: PHP

Комментарии (6)

Артём Кашевар
01-10-2014
Согласен на 100%
Хоть кто-то в рунете думает здраво, респект и удачи!
volter9
02-01-2015
А вообще, лучше поместить все глобальные переменные в отдельный файл и там и всех определять (что бы не надо было искать по всему приложению/библиотеке и подключить в начале приложения.
akuchkovsky
13-01-2015
Полностью согласен!
Кирилл
12-09-2015
То, как Вы описали синглтон, говорит о Вас, как о "профессионале". Достаточно $link сделать приватной и равной null по умолчанию, и в функции connect проверять не на пустоту, а на is_null, и проблемы с переопределением исчезнут. А насчет include_path: зачем создавать массив вне той функции, в которой и ТОЛЬКО в которой он в принципе будет использоваться?
slava
13-09-2015
Суть темы похоже есть о правильном отношении к глобальным переменным но ни о синглтоне. Не думаю, что по тому как написан синглтон в статье, можно судить о професионализме програмиста. К тому же не факт что в следующей версии php "пусто" "0" "null" будут на тех же местах.
twin
16-09-2015
Если свойство объявить приватным, как к нему обращаться извне класса? Допустим в функции mesqli_real_escape_string(). Она первым параметром как раз требует этот линк.
Суть как раз в том, чтобы сделать свойство доступным везде, дабы заменить ей global. А это в синглтоне чревато переопределением, так же как и любая глобальная переменная. Оттого сейчас синглтон все чаще объявляют антипаттерном.

 
Наверх