В этой статье показано несколько способов создания настраиваемых приложений PHP. В статье также исследуются идеальные точки конфигурации приложения и ищется баланс между слишком настраиваемым и слишком закрытым приложением.
Если вы планируете сделать свое PHP-приложение доступным для других людей или компаний, вам необходимо убедиться, что приложение можно настраивать. Как минимум, разрешите пользователям устанавливать логины и пароли базы данных безопасным способом, чтобы материалы в них не были обнародованы.
В этой статье демонстрируется несколько методов хранения параметров конфигурации и редактирования этих параметров. Кроме того, в статье также приведены рекомендации о том, какие элементы необходимо сделать настраиваемыми и как избежать дилеммы чрезмерной или недостаточной настройки.
Конфигурация с использованием файлов INI
PHP имеет встроенную поддержку файлов конфигурации. Это достигается с помощью механизма файла инициализации (INI), такого как файл php.ini, в котором определяются такие константы, как тайм-ауты подключения к базе данных или способ хранения сеансов. При желании вы можете настроить конфигурацию своего приложения в этом файле php.ini. Чтобы проиллюстрировать это, я добавил следующие строки кода в файл php.ini.
myapptempdir=foo
Затем я написал небольшой PHP-скрипт для чтения этого элемента конфигурации, как показано в листинге 1.
Листинг 1. ini1.php
<?php
функция get_template_directory()
{
$v = get_cfg_var("myapptempdir");
return ($v == null)? "tempdir": $v;
}
echo( get_template_directory()."n");
?>
Когда вы запустите этот код в командной строке, вы получите следующие результаты:
% php ini1.php
фу
%
чудесный. Но почему мы не можем использовать стандартную функцию INI для получения значения элемента конфигурации myapptempdir? Я провел небольшое исследование и обнаружил, что в большинстве случаев пользовательские элементы конфигурации не могут быть получены с помощью этих методов. Однако он доступен с помощью функции get_cfg_var.
Чтобы упростить этот подход, инкапсулируйте доступ к переменной во второй функции, которая принимает имя конфигурационного ключа и значение по умолчанию в качестве параметров, как показано ниже.
Листинг 2.
Функция ini2.php get_ini_value( $n, $dv )
{
$c = get_cfg_var($n);
возврат ($c == null)? $dv: $c;
}
функция get_template_directory()
{
return get_ini_value("myapptempdir", "tempdir");
}
Это хороший обзор того, как получить доступ к файлу INI, поэтому, если вы хотите использовать другой механизм или сохранить файл INI где-то еще, вам не нужно беспокоиться об изменении множества функций.
Я не рекомендую использовать INI-файлы для настройки приложения по двум причинам. Во-первых, хотя это облегчает чтение INI-файла, оно делает практически невозможным безопасную запись INI-файла. Так что это подходит только для элементов конфигурации, доступных только для чтения. Во-вторых, файл php.ini используется всеми приложениями на сервере, поэтому я не думаю, что в этом файле следует записывать элементы конфигурации, специфичные для приложения.
Что вам нужно знать о файлах INI? Самое главное — как сбросить путь включения для добавления элементов конфигурации, как показано ниже.
Листинг 3. ini3.php
<?php
echo( ini_get("include_path")."n" );
ini_set("include_path",
ini_get("include_path").":./mylib" );
echo( ini_get("include_path")."n" );
?>
В этом примере я добавил свой локальный каталог mylib в путь включения, поэтому я могу запрашивать файлы PHP из этого каталога, не добавляя путь к оператору require.
Конфигурация в PHP
Распространенной альтернативой хранению записей конфигурации в INI-файле является использование простого сценария PHP для сохранения данных. Ниже приведен пример.
Листинг 4. config.php
<?php
# Указываем расположение временного каталога
#
$TEMPLATE_DIRECTORY = "временный каталог";
?>
Код, использующий эту константу, выглядит следующим образом.
Листинг 5. php.php
<?php
require_once 'config.php';
функция get_template_directory();
{
глобальный $TEMPLATE_DIRECTORY;
верните $TEMPLATE_DIRECTORY;
}
echo( get_template_directory()."n");
?>
Код сначала содержит файл конфигурации (config.php), а затем вы можете использовать эти константы напрямую.
Использование этой технологии имеет множество преимуществ. Во-первых, если кто-то просто просматривает файл config.php, страница оказывается пустой. Таким образом, вы можете поместить config.php в тот же файл, что и корень вашего веб-приложения. Во-вторых, его можно редактировать в любом редакторе, а в некоторых редакторах даже есть функции раскраски синтаксиса и проверки синтаксиса.
Недостатком этой технологии является то, что она доступна только для чтения, как и файлы INI. Извлечь данные из этого файла проще простого, а вот настроить данные в PHP-файле сложно, а в некоторых случаях даже невозможно.
Следующий альтернативный вариант показывает, как написать систему конфигурации, которая по своей природе доступна как для чтения, так и для записи.
Два предыдущих примератекстовых файлов
подходят для записей конфигурации, доступных только для чтения, но как насчет параметров конфигурации, которые доступны как для чтения, так и для записи? Сначала взгляните на текстовый файл конфигурации в листинге 6.
Листинг 6. config.txt
# Файл конфигурации моего приложения
Название=Мое приложение
TemplateDirectory=tempdir
Это тот же формат файла, что и файл INI, но я написал свой собственный вспомогательный инструмент. Для этого я создал свой собственный класс конфигурации, как показано ниже.
Листинг 7. text1.php
<?php
Конфигурация класса
{
частный $configFile = 'config.txt';
частные $items = массив();
функция __construct() { $this->parse() };
функция __get($id) { return $this->items[$id] };
анализ функции()
{
$fh = fopen($this->configFile, 'r');
в то время как ($l = fgets($fh))
{
if ( preg_match( '/^#/', $l) == false)
{
preg_match( '/^(.*?)=(.*?)$/', $l, $found );
$this->items[$found[1]] = $found[2];
}
}
fclose($fh);
}
}
$c = новая конфигурация();
echo($c->TemplateDirectory."n");
?>
Этот код сначала создает объект конфигурации. Затем конструктор читает config.txt и устанавливает в локальную переменную $items проанализированное содержимое файла.
Затем сценарий ищет TemplateDirectory, который не определен напрямую в объекте. Поэтому вызывается магический метод __get с параметром $id, установленным в «TemplateDirectory», который возвращает значение в массиве $items для этого ключа.
Этот метод __get специфичен для среды PHP V5, поэтому этот сценарий необходимо запускать в PHP V5. Фактически, все сценарии в этой статье необходимо запускать под управлением PHP V5.
При запуске этого скрипта из командной строки вы увидите следующие результаты:
% php text1.php
временный каталог
%
Все ожидаемо, объект читает файл config.txt и получает правильное значение для элемента конфигурации TemplateDirectory.
Но что нужно сделать, чтобы установить значение конфигурации? Создав новый метод и новый тестовый код в этом классе, вы можете получить эту функциональность, как показано ниже.
Листинг 8. text2.php
<?php
Конфигурация класса
{
...
функция __get($id) { return $this->items[$id] }
function __set($id,$v) { $this->items[$id] = $v };
функция синтаксического анализа() { ... }
}
$c = новая конфигурация();
echo($c->TemplateDirectory."n");
$c->TemplateDirectory = 'foobar';
echo($c->TemplateDirectory."n");
?>
Теперь есть функция __set, которая является «двоюродной сестрой» функции __get. Эта функция не получает значение переменной-члена. Эта функция вызывается, когда необходимо установить переменную-член. Тестовый код внизу устанавливает значение и распечатывает новое значение.
Вот что происходит, когда вы запускаете этот код из командной строки:
% php text2.php
временный каталог
Фубар
%
Очень хороший! Но как мне сохранить его в файл, чтобы изменение было зафиксировано? Для этого вам нужно записать файл и прочитать его. Новая функция для записи файлов, как показано ниже.
Листинг 9. text3.php
<?php
Конфигурация класса
{
...
функция сохранения()
{
$нф = '';
$fh = fopen($this->configFile, 'r');
в то время как ($l = fgets($fh))
{
if ( preg_match( '/^#/', $l) == false)
{
preg_match( '/^(.*?)=(.*?)$/', $l, $found );
$nf .= $found[1]."=".$this->items[$found[1]]."n";
}
еще
{
$nf .= $l;
}
}
fclose($fh);
copy($this->configFile, $this->configFile.'.bak' );
$fh = fopen($this->configFile, 'w');
fwrite($fh, $nf);
fclose($fh);
}
}
$c = новая конфигурация();
echo($c->TemplateDirectory."n");
$c->TemplateDirectory = 'foobar';
echo($c->TemplateDirectory."n");
$c->сохранить();
?>
Новая функция сохранения умело манипулирует файлом config.txt. Вместо того, чтобы просто переписать файл с обновленными элементами конфигурации (что приведет к удалению комментариев), я прочитал файл и гибко переписал содержимое массива $items. Таким образом, комментарии в файле сохраняются.
Запустите сценарий в командной строке и выведите содержимое текстового файла конфигурации. Вы можете увидеть следующий вывод.
Листинг 10. Сохранение вывода функции
%php text3.php
временный каталог
Фубар
% кот config.txt
#Файл конфигурации моего приложения
Название=Мое приложение
TemplateDirectory=foobar
%
Исходный файл config.txt теперь обновлен новыми значениями.
Файлы конфигурации XML
Хотя текстовые файлы легко читать и редактировать, они не так популярны, как файлы XML. Кроме того, для XML доступно множество редакторов, которые поддерживают разметку, экранирование специальных символов и многое другое. Так как же будет выглядеть XML-версия файла конфигурации? В листинге 11 показан файл конфигурации в формате XML.
Листинг 11. config.xml
<?xml version="1.0"?>
<конфигурация>
<Название>Мое приложение</Название>
<TemplateDirectory>tempdir</TemplateDirectory>
</config>
В листинге 12 показана обновленная версия класса Configuration, который использует XML для загрузки параметров конфигурации.
Листинг 12. xml1.php
<?php
Конфигурация класса
{
частный $configFile = 'config.xml';
частные $items = массив();
функция __construct() { $this->parse() };
функция __get($id) { return $this->items[$id] };
анализ функции()
{
$doc = новый DOMDocument();
$doc->load($this->configFile);
$cn = $doc->getElementsByTagName("config");
$nodes = $cn->item(0)->getElementsByTagName( "*");
foreach($nodes как $node)
$this->items[$node->nodeName] = $node->nodeValue;
}
}
$c = новая конфигурация();
echo($c->TemplateDirectory."n");
?>
Кажется, у XML есть еще одно преимущество: код проще и легче, чем текстовая версия. Чтобы сохранить этот XML, необходима другая версия функции сохранения, которая сохраняет результат в формате XML вместо текстового формата.
Листинг 13. xml2.php
...
функция сохранения()
{
$doc = новый DOMDocument();
$doc->formatOutput = true
$r = $doc->createElement("конфигурация");
$doc->appendChild($r)
($this->items as $k => $v)
{
$kn = $doc->createElement($k);
$kn->appendChild($doc->createTextNode($v));
$r->appendChild($kn);
}
copy($this->configFile, $this->configFile.'.bak');
$doc->save($this->configFile);
}
...
Этот код создает новую объектную модель XML-документа (DOM), а затем сохраняет все данные из массива $items в эту модель. После завершения этого используйте метод save, чтобы сохранить XML в файл.
Последней альтернативой
использованию базы данных
является использование базы данных для хранения значений элементов конфигурации.Первый шаг — использовать простую схему для хранения данных конфигурации. Ниже представлена простая выкройка.
Листинг 14. Параметры Schema.sql
DROP TABLE IF EXISTS;
Настройки СОЗДАТЬ ТАБЛИЦУ (
идентификатор MEDIUMINT NOT NULL AUTO_INCREMENT,
имя ТЕКСТ,
значение ТЕКСТ,
ПЕРВИЧНЫЙ КЛЮЧ(id)
);
Это требует некоторых корректировок в зависимости от требований приложения. Например, если вы хотите, чтобы элемент конфигурации сохранялся для каждого пользователя, вам необходимо добавить идентификатор пользователя в качестве дополнительного столбца.
Для чтения и записи данных я написал обновленный класс Configuration, показанный на рисунке 15.
Листинг 15. db1.php
<?php
require_once('DB.php');
$dsn = 'mysql://root:пароль@localhost/config';
$db =& DB::Connect( $dsn, array() );
if (PEAR::isError($db)) { die($db->getMessage() }
Конфигурация класса
);
{
частный $configFile = 'config.xml';
частные $items = массив();
функция __construct() { $this->parse() };
функция __get($id) { return $this->items[$id] };
функция __set($id,$v)
{
глобальный $дб;
$this->items[$id] = $v;
$sth1 = $db->prepare( 'УДАЛИТЬ ИЗ настроек WHERE name=?' );
$db->execute($sth1, $id);
if (PEAR::isError($db)) { die($db->getMessage() });
$sth2 = $db->prepare('INSERT INTO settings (id, name, value) VALUES (0, ?, ?)');
$db->execute($sth2, array($id, $v));
if (PEAR::isError($db)) { die($db->getMessage() });
}
функция синтаксического анализа()
{
глобальный $дб;
$doc = новый DOMDocument();
$doc->load($this->configFile);
$cn = $doc->getElementsByTagName("config");
$nodes = $cn->item(0)->getElementsByTagName( "*");
foreach($nodes как $node)
$this->items[$node->nodeName] = $node->nodeValue;
$res = $db->query('ВЫБЕРИТЕ имя,значение ИЗ настроек');
if (PEAR::isError($db)) { die($db->getMessage() });
while($res->fetchInto($row) ) {
$this->items[$row[0]] = $row[1];
}
}
}
$c = новая конфигурация();
echo($c->TemplateDirectory."n");
$c->TemplateDirectory = 'новый foo';
echo($c->TemplateDirectory."n");
?>
На самом деле это гибридное решение для работы с текстом и базой данных. Пожалуйста, обратите внимание на метод анализа. Этот класс сначала читает текстовый файл, чтобы получить начальное значение, затем читает базу данных, чтобы обновить ключ до последнего значения. После установки значения ключ удаляется из базы данных и добавляется новая запись с обновленным значением.
Интересно посмотреть, как работает класс Configuration в нескольких версиях этой статьи. Он может читать данные из текстовых файлов, XML и баз данных, сохраняя при этом один и тот же интерфейс. Я призываю вас использовать интерфейсы с такой же стабильностью и в вашей разработке. Клиенту объекта неясно, как именно это работает. Ключом является договор между объектом и клиентом.
Что такое конфигурация и как ее настроить?
Найти золотую середину между слишком большим количеством параметров конфигурации и недостаточным количеством конфигурации может быть сложно. Разумеется, любая конфигурация базы данных (например, имя базы данных, пользователь и пароль базы данных) должна быть настраиваемой. Кроме того, у меня есть несколько основных рекомендуемых элементов конфигурации.
В расширенных настройках каждая функция должна иметь отдельную опцию включения/выключения. Разрешите или отключите эти параметры в зависимости от их важности для приложения. Например, в приложении веб-форума функция задержки включена по умолчанию. Однако уведомления по электронной почте по умолчанию отключены, поскольку это требует настройки.
Все параметры пользовательского интерфейса (UI) должны быть установлены в одном месте. Структура интерфейса (например, расположение меню, дополнительные пункты меню, URL-адреса, ссылающиеся на определенные элементы интерфейса, используемые логотипы и т. д.) должна быть установлена в одном месте. Я настоятельно не рекомендую указывать записи шрифта, цвета или стиля в качестве элементов конфигурации. Они должны быть установлены с помощью каскадных таблиц стилей (CSS), а система конфигурации должна указать, какой файл CSS использовать. CSS — это эффективный и гибкий способ установки шрифтов, стилей, цветов и многого другого. Существует множество отличных инструментов CSS, и ваше приложение должно эффективно использовать CSS, а не пытаться установить стандарт самостоятельно.
Для каждой функции я рекомендую установить от 3 до 10 параметров конфигурации. Эти параметры конфигурации должны быть названы осмысленным образом. Если параметры конфигурации можно задать через пользовательский интерфейс, имена параметров в текстовых файлах, XML-файлах и базах данных должны быть напрямую связаны с заголовком элемента интерфейса. Кроме того, все эти параметры должны иметь четкие значения по умолчанию.
В общем, должны быть настраиваемы следующие параметры: адреса электронной почты, какой CSS использовать, расположение системных ресурсов, на которые ссылаются файлы, и имена файлов графических элементов.
Для графических элементов вы можете создать отдельный тип профиля, называемый скином, который содержит настройки профиля, включая размещение файлов CSS, размещение графики и тому подобное. Затем позвольте пользователю выбирать из нескольких файлов скинов. Это упрощает масштабные изменения внешнего вида вашего приложения. Это также дает пользователям возможность использовать обложку приложения для разных установок продукта. В этой статье не рассматриваются эти файлы скинов, но основы, которые вы здесь изучите, значительно упростят поддержку файлов скинов.
Заключение.
Возможность настройки является жизненно важной частью любого приложения PHP и должна быть центральной частью дизайна с самого начала. Я надеюсь, что эта статья поможет вам реализовать вашу архитектуру конфигурации и предоставит некоторые рекомендации о том, какие параметры конфигурации следует разрешить.