Безопасные альтернативы PHP Globals (Хорошая практика кодирования) - PullRequest
10 голосов
/ 03 сентября 2011

В течение многих лет я использовал global $var,$var2,...,$varn для методов в моем приложении.Я использовал их для двух основных реализаций:

Получение уже установленного класса (например, соединение с БД) и передача информации функциям, отображаемым на странице.

Пример:

$output['header']['log_out'] = "Log Out";
function showPage(){
     global $db, $output;
     $db = ( isset( $db ) ) ? $db : new Database();
     $output['header']['title'] = $db->getConfig( 'siteTitle' );
     require( 'myHTMLPage.html' );
     exit();
}

Тем не менее, это может сказаться на производительности и безопасности.

Какую альтернативную практику я могу использовать, чтобы сохранить свои функциональные возможности, но улучшить дизайн, производительность и/ или безопасность?

Это первый вопрос, который я когда-либо задавал по SO, поэтому, если вам нужны пояснения, пожалуйста, прокомментируйте!

Ответы [ 6 ]

14 голосов
/ 09 июня 2012

1.Глобал.Работает как шарм.Глобалы ненавидят, поэтому мои мысли не использовать его.

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

2.Определите константу в моем файле config.php.

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

3.Включите файл конфигурации в функцию.

Я бы посчитал это издержками.Вы сегментируете кодовую базу для небольшого выигрыша.«Глобальный» здесь станет именем файла, который вы вводите между прочим.


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

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

Внедрение зависимостей

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

Допустим, массив конфигурации - это зависимость, которую мы хотели бы внедрить,Давайте назовем это config и назовем переменную $config.Поскольку это массив, мы можем указать его как array.Прежде всего, определите конфигурацию во включаемом файле. Может быть, вы также можете использовать parse_ini_file, если предпочитаете формат ini-файла.Я думаю, что это даже быстрее.

config.php:

<?php
/**
 * configuration file
 */
return array(
    'db_user' => 'root',
    'db_pass' => '',
);

Этот файл может просто потребоваться в вашем приложении, где бы вы ни захотели:

$config = require('/path/to/config.php');

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

class DBLayer
{
    private $config;

    public function __construct(array $config)
    {
        $this->setConfig($config);
    }

    public function setConfig(array $config)
    {
        $this->config = $config;
    }

    public function oneICanNotChange($paramFixed1, $paramFixed2)
    {
        $user = $this->config['db_user'];
        $password = $this->config['db_pass'];
        $dsn = 'mysql:dbname=testdb;host=127.0.0.1';
        try {
            $dbh = new PDO($dsn, $user, $password);
        } catch (PDOException $e) {
            throw new DBLayerException('Connection failed: ' . $e->getMessage());
        }

        ...
}

Этот пример немного грубоват, ноимеет два примера внедрения зависимости.Сначала через конструктор:

public function __construct(array $config)

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

Второй пример - использование открытого метод установки :

public function setConfig(array $config)

Позволяет добавить зависимость позже, но некоторым методам может потребоваться проверить наличие доступных объектов перед выполнением их работы.Например, если бы вы могли создать объект DBLayer без предоставления конфигурации, метод oneICanNotChange можно было бы вызвать без того, чтобы этот объект имел конфигурацию, и ему пришлось бы иметь дело с этим (что не показано в этом примере).

Сервисный локатор

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

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

Он состоит из одного Services объекта, который предлагает все ваши сервисы, например config сверху.Поскольку при запуске сценария PHP мы еще не знаем, нужен ли вообще какой-либо сервис (например, мы не можем выполнить какой-либо запрос к базе данных, поэтому нам не нужно создавать экземпляр базы данных), он также предлагает некоторую ленивую функцию инициализации.Это делается с помощью фабричных скриптов, которые являются просто PHP-файлами, которые устанавливают службу и возвращают ее.

Первый пример: допустим, функция oneICanNotChange была бы не частью объекта, а простофункция в глобальном пространстве имен.Мы не смогли бы внедрить config зависимость.Вот где появляется объект Services Service Locator:

$services = new Services(array(
    'config' => '/path/to/config.php',
));

...

function oneICanNotChange($paramFixed1, $paramFixed2)
{
    global $services;
    $user = $services['config']['db_user'];
    $password = $services['config']['db_pass'];

    ...

Как уже показывает пример, объект Services отображает строку 'config' в путь к файлу PHP, который определяет$config массив: /path/to/config.php.Он использует интерфейс ArrayAccess, чем предоставляет эту службу внутри функции oneICanNotChange.

Я предлагаю здесь интерфейс ArrayAccess, потому что он хорошо определен и показывает, что мы имеемкакой-то динамический характер здесь.С другой стороны, это позволяет нам ленивую инициализацию:

class Services implements ArrayAccess
{
    private $config;
    private $services;

    public function __construct(array $config)
    {
        $this->config = $config;
    }
    ...
    public function offsetGet($name)
    {
        return @$this->services[$name] ?
            : $this->services[$name] = require($this->config[$name]);
   }
   ...
}

Эта примерная заглушка просто требует заводских сценариев, если она еще этого не сделала, в противном случае она вернет возвращаемое значение сценариев, как массив, объектили даже строку (но не NULL, что имеет смысл).

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

8 голосов
/ 03 сентября 2011

Альтернатива называется внедрение зависимости . В двух словах это означает, что вы передаете данные, которые функция / класс / объект требует в качестве параметров.

function showPage(Database $db, array &$output) {
    ...
}


$output['header']['log_out'] = "Log Out";
$db = new Database;

showPage($db, $output);

Это лучше по ряду причин:

  • локализация / инкапсуляция / функциональность пространства имен (тело функции больше не имеет неявных зависимостей от внешнего мира, и наоборот, теперь вы можете переписать любую часть без необходимости перезаписывать другую, пока вызов функции не изменяется)
  • позволяет выполнять модульное тестирование, поскольку вы можете тестировать функции изолированно, без необходимости настройки конкретного внешнего мира
  • понятно, что функция собирается делать с вашим кодом, просто взглянув на сигнатуру
7 голосов
/ 03 сентября 2011

Однако это может сказаться на производительности и безопасности.

Сказать по правде, производительности и безопасности не существует.Использование глобальных переменных - это вопрос более чистого кода, и ничего более.(Хорошо, хорошо, если вы не передаете переменные размером в десятки мегабайт)

Итак, вы должны сначала подумать, сделают ли альтернативы код чище для вас, илинет.

В вопросах более чистого кода я бы испугался, если бы увидел соединение db в функции с именем showPage.

2 голосов
/ 03 сентября 2011

Один из вариантов, который может не понравиться некоторым людям, - создать singleton объект, отвечающий за сохранение состояния приложения. Если вы хотите получить доступ к некоторому общему «глобальному» объекту, вы можете сделать такой вызов: State::get()->db->query(); или $db = State::get()->db;.

Я считаю этот метод разумным подходом, поскольку он избавляет от необходимости обходить кучу объектов повсюду.

EDIT:

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

class State {
    private static $instance;
    private $_db;

    public function getDB() {
        if(!isset($this->_db)){ 
            // or call your database initialization code or set this in some sort of
            // initialization method for your whole application
            $this->_db = new Database();
        }
        return $this->_db;
    }

    public function getOutput() {
        // do your output stuff here similar to the db
    }

    private function __construct() { }

    public static function get() {
        if (!isset(self::$instance)) {
            $className = __CLASS__;
            self::$instance = new State;
        }
        return self::$instance;
    }

    public function __clone() {
        trigger_error('Clone is not allowed.', E_USER_ERROR);
    }

    public function __wakeup() {
        trigger_error('Unserializing is not allowed.', E_USER_ERROR);
    }
}

и ваша функция показа страницы может выглядеть примерно так:

function showPage(){
     $output = State::get()->getOutput();
     $output['header']['title'] = State::get()->getDB()->getConfig( 'siteTitle' );
     require( 'myHTMLPage.html' );
     exit();
}

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

function showPage($state){
     $output = $state->getOutput();
     $output['header']['title'] = $state->getDB()->getConfig( 'siteTitle' );
     require( 'myHTMLPage.html' );
     exit();
}

$state = new State; // you'll have to remove all the singleton code in my example.
showPage($state);
0 голосов
/ 09 июня 2012

Начните проектировать ваш код в ООП, затем вы можете передать config в конструктор.Вы также можете инкапсулировать все свои функции в класс.

<?php 
class functions{
    function __construct($config){
        $this->config = $config;
    }

    function a(){
        //$this->config is available in all these functions/methods
    }

    function b(){
        $doseSomething = $this->config['someKey'];
    }

    ...

}


$config = array(
'someKey'=>'somevalue'
);

$functions = new functions($config);

$result = $functions->a();
?>

Или, если вы не можете реорганизовать скрипт, выполните цикл по массиву config и определите константы.

foreach($config as $key=>$value){
    define($key,$value);
}
0 голосов
/ 03 сентября 2011
function showPage(&$output, $db = null){
     $db = is_null( $db )  ? new Database() : $db;
     $output['header']['title'] = $db->getConfig( 'siteTitle' );
     require( 'myHTMLPage.html' );
     exit();
}

и

$output['header']['log_out'] = "Log Out";
showPage($output);

 $db =new Database();
showPage($output,$db);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...