Использование глобальных переменных в PHP

PHPit.net logoКак-то я заинтересовался вопросом использования глобальных переменных в PHP-скриптах. В результате нашел статью (Using globals in PHP с PHPit.net), хорошо освещающую этот вопрос. Ниже ее вольный перевод.

Краткое содержание

В этой статье я покажу вам как правильно использовать глобальные переменные в PHP. Мы рассмотрим глобальные ключевые слова, аргументы функций и паттерны проектирования Singleton и Registry.

Введение

Всякий раз когда вы разрабатываете масштабный PHP-скрипт вам приходится использовать глобальные переменные, с того времени как вам понадобится использовать некоторые данные в разных частях вашего скрипта. Хорошими примерами таких данных являются: настройки скрипта, параметры соединения с базой данных, идентификатор юзера и другие. Есть много путей сделать эту информацию глобальной (с точки зрения доступности), но чаще всего используется метод с использованием ключевого слова global, который мы рассмотрим позже в этой статье.

Есть одна проблема при использовании ключевого слова global которая является плохим примером программирования и может привести к проблемам впоследствии. Она заключается в том, что все отдельные части скрипта связываеются в таком методе воедино и часто если вы делаете изменения в одной части что-то ломается в другой. Если вы используете больше чем несколько штук глобальных переменных ваш код со временем станет очень сложно-поддерживаемым.

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

Использование глобальных переменных и ключевое слово globals

По умолчанию PHP объявляет несколько переменных называемых суперглобальными которые становятся глобальными автоматически и доступны для использования из любого места скрипта, например суперглобальные массивы $_GET or $_REQUEST. Они главным образом используются для получения данных из форм и других внешних данных, и вреда от их использования нет, так как в них ничего не записывается по умполчанию.

Но вы можете использовать и собственные глобальные переменные с ключевым словом global, которое используется для импорта переменных из глобальной области видимости в локальную область видимости функции. Если вы не знаете что я имею ввиду под областью видимости, посмотрите документацию, раздел PHP Variable Scope documentation.

Следующий пример показывает использование ключевого слова global:

<?php
$my_var = 'Hello World';
 
test_global();
 
function test_global() {
	// Здесь, в локальной области видимости
    // функции переменная $my_var не существует
 
    // Вызовет ошибку: "Undefined variable: my_var"
    echo $my_var;
 
    // Теперь давайте импортируем переменную
    global $my_var;
 
    // Теперь работает:
    echo $my_var;
}
 
?>

Как вы видите в примере, ключевое слово global используется для импорта переменной из глобальной области видимости. Выглядит красиво и просто, почему же вы должны волноваться о использовании ключевого слова keyword?

Есть три хороших причины:

1. Использовать повторно часть скрипта невозможно.
Если определенная функция зависит от глобальной переменной, становится невозможным ее использование в другом контексте. Также невозможно будет использовать эту функцию в другом скрипте.
2. Усложняется поиск ошибок
Отслеживание глобальных переменных намного сложнее локальных. Глобальная переменная может быть объявлена в подключаемом файле, на поиски которого можно потратить несколько часов, хотя хороший редактор кода / IDE помогут сделать это быстрее.
3. Усложняется разбор кода, особенно по прошествии длительного времени.
Сложно понять где была объявлена пользовательская глобальная переменная и что она делает. Вы можете знать все о каждой вашей глобальной переменной в процессе разработки, но через год вы вероятно забудете о половине из них, и будете укорять себя за использование такого их количества.

Итак если мы не можем использовать ключевое слово global, что же нам использовать? Давайте рассморим нескольно решений.

Использование аргументов функций

Один путь перестать использовать ключевое слово global это передача значений в аргументы функции, например:

<?php
$var = 'Hello World';
 
test ($var);
 
function test($var) {
    echo $var;
}
 
?>

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

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

<?php
$db = new DBConnection;
$settings = new Settings_XML;
$user = new User;
 
test($db, $settings, $user);
 
function test(&$db, &$settings, &$user) {
        // Блок действий
}
 
?>

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

Использование паттерна проектирования Singleton

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

<?php
 
// Берем экземпляр объекта DBConnection
$db =& DBConnection::getInstance();
 
// Устанавливаем свойство user объекта
$db->user = 'sa';
 
// Устанавливаем вторую переменную (ссылающуюся на тот же экземпляр объекта)
$second =& DBConnection::getInstance();
 
// Должно напечатать 'sa'
echo $second->user;
 
Class DBConnection {
    var $user;
 
    function &getInstance() {
            static $me;
 
            if (is_object($me) == true) {
                    return $me;
            }
 
            $me = new DBConnection;
            return $me;
    }
 
    function connect() {
            // TODO
    }
 
    function query() {
            // TODO
    }
 
}
 
?>

Одной из важных частей примера является функция getInstance(). Эта функция позволяет создать (и вернуть) только один экземпляр объекта класса DBConnection, используя статическую переменную $me (прим пер.: Example #5 Example use of static variables http://php.net/manual/en/language.variables.scope.php).

Преимущество использования Singleton-а в том что нам не надо явно передавать объект, т.к. его можно получить вызовом функции getInstance(), например:

<?php
 
function test() {
    $db = DBConnection::getInstance();
 
    // Делаем что-то с объектом
 
}
 
?>

Есть и недостатки такого использования Singleton-ов. Во-первых, мы не можем использовать несколько объектов одного класса (отсюда название singletons). Во-вторых, singleton-ы невозможно протестировать с помощью модульного тестирования. Это практически невозможно, если не использовать некоторые хаки, которые использовать не хочется. Вот почему singleton-ы не являеются магическим решением проблемы, которое мы ищем.

Паттерн Registry

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

Простой объект класса Registry выглядит так:

<?php
 
Class Registry {
        var $_objects = array();
 
        function set($name, &$object) {
                $this->_objects[$name] =& $object;
        }
 
        function &get($name) {
                return $this->_objects[$name];
        }
}
 
?>

Первый шаг использования класса Registry является регистрация объектов с использованием метода set():

<?php
$db = new DBConnection;
$settings = new Settings_XML;
$user = new User;
 
// Регистрируем объекты, созданные выше
$registry =& new Registry;
$registry->set ('db', $db);
$registry->set ('settings', $settings);
$registry->set ('user', $user);
 
?>

Теперь в объекте(массиве) класса Registry содержатся все наши пользовательские объекты. Теперь мы можем подать этот один объект на функцию, вместо трех пользовательских, например так:

<?php
function test(&$registry) {
        $db =& $registry->get('db');
        $settings =& $registry->get('settings');
        $user =& $registry->get('user');
 
        // Какие-то действия с объектами
 
}
?>

И что еще лучше нам не надо менять что-либо если мы добавляем свой новый объект в наш скрипт. Нам потребуется всего лишь зарегистрировать его в методом класса Registry и он незамедлительно станет доступен всем компонентам.

Чтобы упростить использование класса Registry, модифицируем его в singleton, так как объект класса Registry в нашем скрипте должен быть только один. Добавим следующий метод в класс Registry:

function &getInstance() {
        static $me;
 
        if (is_object($me) == true) {
                return $me;
        }
 
        $me = new Registry;
        return $me;
}

Теперь используем класс Registry как Singleton:

<?php
$db = new DBConnection;
$settings = new Settings_XML;
$user = new User;
 
// Регистрируем наши объекты
$registry =& Registry::getInstance();
$registry->set ('db', $db);
$registry->set ('settings', $settings);
$registry->set ('user', $user);
 
function test() {
        $registry =& Registry::getInstance();
        $db =& $registry->get('db');
        $settings =& $registry->get('settings');
        $user =& $registry->get('user');
 
        // Какие-то действия с объектами
 
}
 
?>

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

Класс-обертка Request

Хотя наш класс Registry делает излишним использование глобальных ключевых слов, в нашем скрипте все еще остается один тип глобальных переменных: Суперглобальные массивы, такие как $_POST и $_GET. Хотя эти переменные стандартные и их использование не имеет особого значения для вас, в некоторых случаях вам возможно захочется использовать класс Registry и для них.

Простым решением этой задачи является небольшой класс который будет осуществлять доступ к этим переменным, который часто называют запрос-обертка, и выглядит примерно так:

<?php
 
Class Request {
        var $_request = array();
 
        function Request() {
                // Получаем глобальную переменную
                $this->_request = $_REQUEST;
        }
 
        function get($name) {
                return $this->_request[$name];
        }
}
 
?>

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

Пример использования класса Request:

<?php
$request = new Request;
 
// Register object
$registry =& Registry::getInstance();
$registry->set ('request', &$request);
 
test();
 
function test() {
        $registry =& Registry::getInstance();
        $request =& $registry->get ('request');
 
        // Выведет строку 'name', в обычной ситуации это $_GET['name']
        echo htmlentities($request->get('name'));
}
 
?>

Как вы видите мы больше не используем ключевое слово global и освободили функции от всех глобальных переменных.

Заключение

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

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

Источник: http://www.phpit.net/article/using-globals-php/

Комментариев: 7

  • 12.11.2010 SpYeR:

    Спасибо, хороший перевод.

    А оригинал по ссылке почему-то «Closed».

    • 12.11.2010 Кирилл:

      Спасибо :) .

      Да, причем давненько, спас то ли кэш гугла, то ли вэбархив.

  • 30.12.2010 Eugene Che:

    Все хорошо. Но непонятно, почем бы не сделать это все в __construct. Ведь он для этого и нужен. В __construct добавляем проверку на существования класса и вуаля.Можно вызывать как $registry = new Registry;

    А да... Неполучится так...

  • 12.05.2011 mmmm:

    Спасибо, помогло!

  • 22.01.2014 Дмитрий:

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

    • 27.02.2015 K10:

      А это по сути и есть та же самая куча глобальных переменных, только обернутая в класс. Смысл в этом слабо угадывается.

      • 26.12.2015 Alexey Volkov:

        Смысл на самом деле есть. Например, централизованное хранилише настроек проекта с единой точкой доступа (характер логирования или реакции на ошибки). Все это может использоваться во многих классах проекта, но при этом поведение может быть изменяемо по ситуации.


Добавление комментария:

 css.php