Кой е най-добрият начин за запазване на конфигурационни променливи в PHP уеб приложение?

Често превключвам между .NET и PHP разработка. Със ASP.NET сайтове запазвам конфигурационна информация (напр. низове за връзка, директории, настройка на приложението) във файла web.config, който е подходящо защитен и лесен за достъп до стойностите, и т.н.

В PHP решавам това с клас, който има статични методи за всяка променлива:

class webconfig {
    public static function defaultPageIdCode() {
        return 'welcome';
    }
}

Файлът е включен от променливите на приложението, които са достъпни с един ред:

$dp = webconfig::defaultPageIdCode();

И тъй като PHP не е компилиран, лесно е да влезете с telnet и така или иначе да промените стойност за уебсайт, така че това решение работи доста добре и ми дава тези две предимства:

  • Мога да добавя логика към конфигурационна променлива, без да нарушавам нейния интерфейс с приложението
  • тези конфигурационни променливи се показват като intellisense в моите напр. Eclipse, NetBeans и др.

Но мога да си представя, че има други начини, по които хората решават запазването на настройките на уеб конфигурацията в PHP, които може да имат други предимства.

Особено тези, които имат опит с редица PHP рамки, какви са другите начини за запазване на конфигурационните променливи и техните предимства и недостатъци?


person Edward Tanguay    schedule 16.09.2010    source източник
comment
възможен дубликат на PHP, файл за четене - това е за четене на конфигурационни файлове в различни формати. Дали ще поставите клас отгоре, който предоставя изричен API за стойностите, зависи от вас.   -  person Gordon    schedule 16.09.2010


Отговори (11)


Реших да изброя всички известни методи заедно с техните предимства и недостатъци.

Маркирах този отговор като уики на общността, така че сътрудничеството е по-лесно.


Глобални константи

Присвояване:

  • define('CONFIG_DIRECTIVE', 'value');

Достъп до:

  • $object = new MyObject(CONFIG_DIRECTIVE);

Предимства:

  • Има глобален обхват.
  • Автоматично довършване от повечето IDE.
  • Има договорена конвенция за именуване (UPPERCASE_UNDERSCORE_SEPARATED).

Недостатъци:

  • Директивите не могат да съдържат масиви (преди v7.0.0).

Специални бележки:

  • Не може да се преназначава.

Алтернативни синтактични файлове

Например: XML, INI, YAML и др.

Присвояване:

  • Просто редактирайте файла на неговия специфичен език. (Например за INI файлове: config_directive = value.)

Достъп до:

  • Конфигурационният файл трябва да бъде анализиран. (Например за INI: parse_ini_file().)

Предимства:

  • Най-вероятно има синтаксис, по-подходящ за конфигурационен файл.

Недостатъци:

  • Възможни разходи за достъп и анализ на файла.

Масив

Присвояване:

  • $config['directive'] = 'value';

Достъп до:

  • The cleanest method of accessing configuration values using this method is to pass the required values to the object that needs them on creation, or pass them to your container object and let it handle passing them out internally.
    • $object = new MyObject($config['directive']);
    • $container = new MyContainer($config);

Предимства:

  • Директивите могат да бъдат масиви.

Недостатъци:

  • Без автоматично довършване.

Специални бележки:

  • Могат да възникнат променливи сблъсъци. Ако това ви притеснява, наименувайте масива си по подходящ начин, за да ги избегнете.

Клас

Присвояване:

  • There are many different class based implementations.
    • Static class.
      • myCfgObj::setDirective('DIRECTIVE', 'value');
    • Instanced class.
      • myCfgObj->setDirective('DIRECTIVE', 'value');

Достъп до:

  • Again there are various class based implementations.
    • Static class.
      • $object = new MyObject(myCfgObj::getDirective('DIRECTIVE'));
    • Instanced class.
      • $object = new MyObject(myCfgObj->getDirective('DIRECTIVE'));

Предимства:

  • Може да се зарежда автоматично.

Недостатъци:

  • Има тенденция да бъде малко многословен.
  • Може да бъде трудно да се поддържа, ако не се използва клас контейнер.
person Community    schedule 16.09.2010
comment
@последен редактор: Какво, по дяволите, ако искахте съвсем друг отговор, можехте да го публикувате! Вие го променихте напълно и въпреки че съм съгласен, някои от промените са добре дошли, логиката за достъп е напълно обратна! Искате да ми кажете, че глобалните конфигурационни променливи трябва да се предават, където и да се използват?? Константите на директорията, стойностите на средата, настройките за локализация са само част от константите, които трябва да се използват в цялото приложение и трябва да бъдат достъпни възможно най-лесно, а не да се изпращат една по една като параметри или полета! - person raveren; 17.09.2010
comment
Освен това, как предполагате, че трябва да работи автоматичното зареждане, ако не са зададени пътеки към системната директория? Summa summarum, няма да се връщам назад, но повече няма да участвам в този отговор. - person raveren; 17.09.2010
comment
Този отговор беше пълна бъркотия и беше ИЗКЛЮЧИТЕЛНО грешен. Например често срещан недостатък е дали конфигурационната стойност е лесно вградена в низ. 1) стойността на конфигурацията рядко трябва да бъде вградена в низ и 2) когато го направите, тя не трябва да бъде вградена в двойни кавички. Това е УЖАСНА практика и е ужасна за видимостта. - person anomareh; 17.09.2010
comment
Що се отнася до предаването на конфигурационните стойности, това беше само по отношение на използването на масив. — Обектите са съставени от методи. Може би мислите за функции? Казах, че те трябва да бъдат предадени на обектите, които ги изискват. — Ако имате нужда от конфигурационни стойности НАВСЯКЪДЕ във вашето приложение, а не само в задната част, трябва да изберете различен метод за предоставяне на вашите конфигурационни стойности за вашето приложение. — Що се отнася до вашето автоматично зареждане, може би трябва да разгледате __DIR__, __FILE__ и dirname(). - person anomareh; 17.09.2010

Склонен съм да използвам статичен клас Settings в PHP, това е така, защото

  • Има глобален обхват.
  • Можете да активирате/деактивирате промени в защитени конфигурации.
  • Можете да добавите всякакви настройки по време на всяко място в рамките на времето за изпълнение.
  • Можете да направите класа автоматизиран, за да извлича публични конфигурации от файл/база данни.

Пример:

abstract class Settings
{
    static private $protected = array(); // For DB / passwords etc
    static private $public = array(); // For all public strings such as meta stuff for site

    public static function getProtected($key)
    {
        return isset(self::$protected[$key]) ? self::$protected[$key] : false;
    }

    public static function getPublic($key)
    {
        return isset(self::$public[$key]) ? self::$public[$key] : false;
    }

    public static function setProtected($key,$value)
    {
        self::$protected[$key] = $value;
    }

    public static function setPublic($key,$value)
    {
        self::$public[$key] = $value;
    }

    public function __get($key)
    {//$this->key // returns public->key
        return isset(self::$public[$key]) ? self::$public[$key] : false;
    }

    public function __isset($key)
    {
        return isset(self::$public[$key]);
    }
}

След това във вашето време за изпълнение, ако сте заредили първо този файл, последван от конфигурационния файл на вашата база данни, конфигурационният файл на вашата база данни ще изглежда така:

<?php
Settings::setProtected('db_hostname', 'localhost');
Settings::setProtected('db_username', 'root');
Settings::setProtected('db_password', '');
Settings::setProtected('db_database', 'root');
Settings::setProtected('db_charset', 'UTF-8');
//...
echo Settings::getProtected('db_hostname'); // localhost
//...
Settings::setPublic('config_site_title', 'MySiteTitle');
Settings::setPublic('config_site_charset', 'UTF-8');
Settings::setPublic('config_site_root', 'http://localhost/dev/');

Както можете да видите, имаме метод __get, на който трябва да бъде разрешено само да хваща публични променливи. Пример защо имаме това е следният:

$template = new Template();
$template->assign('settings', new Settings());

Независимо от факта, че сме използвали този обект като статичен обект, стойностите все още трябва да стоят така в шаблона, който сега можете да направите, да речем.

<html>
    <head>
        <?php echo isset($settings->config_site_title) ? $settings->config_site_title : 'Fallback Title'; ?>
    </head>
</html>

И това ще ви позволи само да имате достъп до публичните данни през инициализирания период.

Това може да стане много по-сложно, но по-удобно за системата, някои примери:

  • Метод loadConfig за автоматично анализиране на конфигурационен файл, xml, php, yaml.
  • Ако регистрирате shutdown_function, можете автоматично да актуализирате базата данни с нови настройки.
  • Можете автоматично да попълвате класа с конфигурация от тази база данни.
  • Можете да внедрите итератори, за да го направите съвместим с цикъла.
  • Много повече.

Това също е най-добрият метод за завършване на тази работа.

person RobertPitt    schedule 16.09.2010
comment
Предимствата наистина са много и не виждам недостатък, освен да имам (мъничко) режийни разходи за извикване на метод. Все пак бих предпочел унифициран get($parameterName) метод. - person raveren; 16.09.2010
comment
Поддържането на двойки ключ/стойност в рамките на масив ви позволява да накарате малък клас да работи сам за себе си, основният принцип на ООП, "Write less, Do more", режийните разходи обаче са много, много незначителни :) - person RobertPitt; 16.09.2010
comment
Да, съгласен съм и с двата указателя, но мисля, че ме разбрахте погрешно за метода get, бих предпочел да го използвам вместо двата getProtected и getPublic. Или имат цел, която аз пропускам? - person raveren; 16.09.2010
comment
Обикновено, когато създавам приложение, имам публична променлива и защитена променлива за разделяне, конфигурацията на GUI от конфигурацията на сървъра, така че разработчикът да знае, че трябва да използва getPublic само когато извлича данни, които ще бъдат използвани от GUI. - person RobertPitt; 16.09.2010
comment
Обърнете внимание, че най-вероятно методът __get никога няма да бъде извикан, тъй като е абстрактен и статичен клас (освен ако не създадете дъщерен клас на Settings клас). Освен това бих върнал NULL или бих повдигнал изключение, ако кодът се опита да получи достъп до незададена стойност, тъй като false е валидна стойност, която може да бъде зададена на параметър, докато NULL обикновено означава undefined. - person raveren; 16.09.2010
comment
Да, причината за __get е, че ако присвоите инстанционен обект на изглед, изгледът може да вижда само публични променливи. - person RobertPitt; 16.09.2010
comment
@RobertPitt Търсих най-добрия начин да запазя конфигурационните променливи и намерих отличното ви решение за отделно поддържане на публични и защитени. Това беше отговорено преди 10 години. Какви са вашите мисли през 2021 г., след 10 години? - person MyO; 18.01.2021

Начинът, по който го правя, е директно да ги съхранявам в array и да запазя файла като config.php

<?php

$config['dbname'] = "mydatabase";
$config['WebsiteName'] = "Fundoo Site";
$config['credits'] = true;
$config['version'] = "4.0.4";

?>

Това е начинът, по който го правят повечето PHP рамки като Wordpress и т.н.

person shamittomar    schedule 16.09.2010
comment
Започнах да правя това също, но едно предимство на статичните методи е, че мога лесно да имам логика към моите променливи, без да нарушавам интерфейса с приложението, напр. може да постави оператор if в defaultPageIdCode(), който връща различна страница въз основа на напр. местоположение на потребителя, информация за бисквитки и др. - person Edward Tanguay; 16.09.2010
comment
@Edward: Ако правите това, вероятно вече не трябва да е в конфигурационен файл. - person Amber; 16.09.2010
comment
@Edward, това е съвсем различно нещо. Давах ти алтернатива на web.config - person shamittomar; 16.09.2010
comment
да, това е доста 1 към 1 с XML файл web.config, често искам да поставя логика и в променливи в web.config и съм принуден да ги поставя другаде, предполагам, че можете също да кажете $config['version'] = getDynamicVersion();. Но друго предимство на статичните методи е, че те изскачат в моя Eclipse intellisense. - person Edward Tanguay; 16.09.2010
comment
има недостатъци на този подход: $config е глобална променлива с общо име, така че трябва а) да пишете global $config; където и да го използвате и б) поради това, че има общо име, може да бъде презаписано по погрешка. - person raveren; 16.09.2010
comment
@Reveren, можете да промените името на променливата $config на произволно име, като $rev_config, за да избегнете конфликти. - person shamittomar; 16.09.2010
comment
Е, това е повече писане/по-малко разбираемо и все пак трябва да направите a). Като алтернатива можете да получите достъп до него чрез $GLOBALS['config'], но това е още повече. Друг недостатък, който не беше споменат по-рано, е, че никоя IDE в момента не поддържа автоматично довършване на масиви (докато всички поддържат автоматично довършване на дефини, ако знаете как започва). - person raveren; 16.09.2010
comment
@Raveren Нямам представа защо бихте използвали global $config; или $GLOBALS[]. Много по-смислено е да подадете необходимите конфигурационни директиви директно или да ги предадете на обект-контейнер, който да бъде прехвърлен вътрешно. - person anomareh; 17.09.2010
comment
@Edward Config стойностите не трябва да са динамични. Когато работите, от началото до края, не трябва да има нужда от промяна на конфигурационната стойност. Например идентификационни данни за база данни, версия на приложението и т.н. — За това, което описвате, трябва да дефинирате константа, която да се използва в началото на скрипта, за да се използва навсякъде, или трябва да ги съхранявате вътрешно, за да се използват. Те не принадлежат към конфигурационен файл. - person anomareh; 17.09.2010
comment
@anomareh, така че трябва да предавате всяка конфигурационна стойност на всеки метод, който се нуждае от нея? За просто изпращане на поща имам нужда от: име на изпращача, адрес на подателя, скрит имейл, текуща среда, локализиран ли е сайтът и основен URL адрес на сайта. Също така, кой сте вие, за да решавате дали нечия логика използва динамични или статични стойности на конфигурацията. - person raveren; 17.09.2010
comment
@Raveren този отговор посочи този метод като най-често срещания начин в рамките. Това е удобен метод за конфигуриране, когато използвате рамка, съставена от обекти, към които казах, че трябва да ги предадете. Може би мислите за функции? В този случай ще откриете, че друг метод на конфигуриране ще бъде от по-голяма полза. - person anomareh; 17.09.2010
comment
@Raveren Що се отнася до това дали стойностите на конфигурацията трябва да са динамични или не, можете да видите, че не съм единственият, който вярва, че те трябва да са статични. Да не говорим, че константите, които обикновено се използват за конфигурационни стойности, са неизменни. Ако стойността е динамична, има вероятност тя да се променя на страница по страница и всъщност не е настройка за конфигурация на приложение и като такава не трябва да се третира като такава. - person anomareh; 17.09.2010

В PHP винаги използвам ".htaccess", за да защитя моя конфигурационен файл (Двойна защита)

person Oyeme    schedule 16.09.2010

Забележка: „Най-добрият начин“ никога не съществува. Всяко приложение и рамка го правят в свой собствен стил. Докато вашият пример върши работа, мисля, че е малко тежък за ресурси за прост конфигурационен файл.

  • Можете да го направите с единични променливи като Amber посочен
  • Можете да го направите с масиви, това е най-често срещаният подход и винаги можете лесно да редактирате вашия конфигурационен файл.
  • Можете да го направите с .ini файлове, които PHP лесно анализира

Редактиране:

Едуард, моля, разгледайте примерите на parse_ini_file. Можете да заредите .ini файла с проста команда, след което можете да използвате променливите в клас като във вашия пример.

person fabrik    schedule 16.09.2010
comment
и не забравяйте yaml файлове, xml файлове ... и така нататък - person teemitzitrone; 16.09.2010
comment
@maggie +1, има почти милиони решения. - person fabrik; 16.09.2010
comment
но с всички тези опции не получавате intellisense, нали? и с напр. ini/xml/и т.н., не можете да добавите никаква логика към вашите конфигурационни променливи, нали? това са два недостатъка, които ме отклоняват от тези подходи - person Edward Tanguay; 16.09.2010
comment
@maggie правилно, но най-добрият начин винаги съществува в контекста, просто искам да чуя кой е най-добрият ви начин, кой е най-добрият ви начин и ще ги разгледам всички и ще разбера най-добрия си начин във всеки контекст :-) - person Edward Tanguay; 16.09.2010
comment
@Edward Обичам yaml файлове, те са четими и лесни за редактиране/разбиране. и все пак, ако имам нужда от специфична конфигурация за да кажем разработка, тогава просто заредете dev-config yaml (вижте symfony(2) yml config за пример) - person teemitzitrone; 16.09.2010
comment
@Edward: с .ini файлове можете да направите много логика на вашите променливи. напр. можете дори да създадете многоизмерни масиви вътре в тях, които могат да бъдат анализирани с клас Config (както във вашия пример) и това е! PHP трябва да направи трудното, не ние! :) - person fabrik; 16.09.2010

Мисля, че има доста възможности, но най-често срещаните методи са съхраняване като обикновен текст във файлове като .csv, .ini, .xml. С малки трикове можете да защитите тези файлове, така че никой да не може да ги зарежда директно.

пример за INI-файл:

;<?php die(); ?>
[config1]
var1 = 'value1';
var2 = 'value2';
...
[config2]
...

; се счита за коментар в ini файлове. Така че, когато четете във файла с ini-парсер, този ред ще бъде игнориран. Ако някой получи достъп до файла директно чрез url, функцията die() ще бъде изпълнена. Това работи само ако INI-файлът носи файлово разширение като .php, така че сървърът да знае, че това трябва да се изпълни, а не да се показва като обикновен текст.

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

Zend_Config е компонент на Zend-Framework, който предоставя възможности за няколко адаптера за съхранение с лесен за използване API.

person Fidi    schedule 16.09.2010
comment
За да работи трикът, ini файлът трябва да има разширение .php (или всяко друго, което apache изпълнява, а не показва - като phtml) - person raveren; 16.09.2010
comment
-1 (хаки). Има стандартни библиотеки и формати за съхраняване на конфигурация, които не разчитат на трикове (те разчитат на стандартни, защитени механизми). Ако искате хранилище на файлове, помислете за json, xml и т.н. (които имат вградени анализатори), в противен случай помислете за хранилища за ключ/стойност (reddis, mongo) или конфигурация, поддържана от база данни. - person Bruce Alderson; 15.09.2013

Тъй като PHP може да използва OO, обичам да използвам „Конфигурационен клас“:

class Config
{
    /**
     * ---------------------------------
     * Database - Access
     * --------------------------------- 
     */

    /**
     * @var String
     */
    const DB_DRIVER = 'pgsql';

    const DB_USER = 'postgres';

    const DB_PASSWORD = 'postgres';

    const DB_NAME = 'postgres';
}

Той е лесно достъпен с Config::DB_DRIVER. Няма нужда да включвате файла, тъй като автоматичното зареждане на приложението ще направи това вместо вас. Разбира се, защитата на файла все още трябва да бъде направена.

person DrColossos    schedule 16.09.2010
comment
Този метод няма предимства пред defines, има недостатъка на последния да не може да използва масиви и има свой уникален недостатък в това, че не може да използва изрази в присвоявания, като 'http://'.$_SERVER['HTTP_HOST'] . '/' или dirname(), които всички са необходими повечето от случаите. - person raveren; 16.09.2010
comment
о, това също не е ООП. Това е просто безсмислена обвивка за променливи. - person raveren; 16.09.2010
comment
@Raveren, има предимство пред define, че не замърсява глобалното пространство. - person Fred; 04.12.2014
comment
Може също така да съдържа масиви, ако не приемете отговора твърде буквално. Просто направете променливата публична и статична. Въпреки това, защо някой някога би искал опция за конфигурация да съдържа повече от една стойност, е извън мен. - person Cypher; 10.01.2015

Telnet? OMG, изпаднах във времето и пристигнах през 1992 г.!

Но сериозно, IIRC има инструменти, които позволяват на asp.net (и други езици) да анализират данните от сесията - което е просто сериализиран php масив. Бих се опитал да внедря глобалните настройки като вид сесия в сянка в PHP. Дори и да не съхранявате конфигурационните си настройки като сериализиран PHP масив, можете да ги картографирате в сесията по време на изпълнение, като използвате собствен манипулатор на сесия.

По отношение на това къде съхранявате данните - това е по-сложно, когато се предполага, че работите на платформа на Microsoft. Очевидно не искате разходите за достъп до диска за всяка заявка. Въпреки че NT прави известно дисково кеширане, той не е (IME) толкова ефективен, колкото другите операционни системи. Memcached изглежда е едно решение за това. Наистина изглежда може да се използва от asp.net.

HTH

person symcbean    schedule 16.09.2010

Обичайният маршрут е да се използва define:

define('MYSQL_USER', 'ROOT');

и достъп до него в цялото приложение чрез MYSQL_USER:

$user = MYSQL_USER;

Масивите обаче не се поддържат по този начин.

person Community    schedule 16.09.2010
comment
това запълва глобалното пространство от имена с константи - person Gordon; 16.09.2010
comment
Но това все още е добър метод и константите трябва да бъдат дефинирани. - person Martin Bean; 16.09.2010
comment
@Gordan, съгласен съм, константите не са единственият обект, който можете да използвате в глобалното пространство. - person RobertPitt; 16.09.2010

Има няколко възможности:

  1. Можете да използвате конфигурационен файл (ini, json, xml или yaml). За ini имате parse_ini_file, за JSON има json_decode (+file_get_contents), за YAML трябва да използвате външна библиотека (потърсете sfYaml)

  2. Можете да имате конфигурационен файл с променливи или константи (по-добре за неизменна конфигурация и достъпни във всички обхвати), които включвате във вашия скрипт:

    define('ROOT_DIR', '\home\www');

    $sRootDir = '\home\www';

Ако сте ориентирани към OO, можете да го обвиете в клас, като свойства - нямате getter метод за всяко свойство, можете просто да имате:

class Config
{
    public $var1 = 'xxx';
    public $var2 = 'yyy';
}

($c = new Config(); print $c->var1)

or

static class Config
{
    public static $var1 = 'xxx';
    public static $var2 = 'yyy';
}

(печат c::$var1)

Най-доброто е да имате клас тип регистър, прилагащ единичен модел и способен да чете конфигурация от даден файл.

person ts.    schedule 16.09.2010

За да бъда тестван, използвам Config клас, който съдържа действителните конфигурационни данни, и AppConfig статичен клас, който съдържа препратка към Config обект, зареден при стартиране от конфигурационни файлове за цялото приложение (зависимост, инжектирана при стартиране). В тестовата среда променям само обекта Config. Вижте https://github.com/xprt64/Config

person Constantin Galbenu    schedule 21.03.2016