Админка на файлах. Урок 2. Конфиги и бекенд
В первой статье об админке мы разобрали простой способ чтения настроек из отдельных файлов.
Плюсы такого подхода:
- Внедряется за 15 минут 10-ю строками кода
- Одна настройка - один файл
Для случаев, когда нужно быстро внедрить и забыть, такое решение подходит.
Минусы:
- 1. Для правки настроек приходится лезть на ftp
- 2. Чем больше настроек, тем больше файлов, а значит и больше шансов ошибиться
- 3. Настройки хранятся только в строковом виде, из чего вытекают два подпункта
- 3.1. При чтении приходится их конвертировать
- 3.2. Опять легко ошибиться: вместо числа вбить строку и поломать приложение
Давайте напишем больше 10 строк кода, но зато эти минусы устраним.
План такой:
- Объединим настройки в один файл
- Реализуем редактирование параметров каждой настройки: название, тип, дефолтные значения
- Поддержим 4 типа настроек: строки, числа, логические и списки.
- Сделаем интерфейс для редактирования настроек
- Поддержим базовую валидацию, чтобы уменьшить риск ошибки пользователей
Бонус: в процессе мы подключим шаблонизатор Twig и разберемся, как он работает. Чуть-чуть.
Разобьем план на 3 логические части:
- Переработка конфигов и серверной части
- Интерфейс админки: чтение настроек. Twig
- Клиентская часть и сохранение настроек
Сегодня первая часть - конфиги и бэкенд.
Значения настроек и конфиги.
Коряво звучит "значения настроек", но по-другому не придумал. Что мы имеем? Есть настройка email и ее значение webdevkin@gmail.com. В первой версии админки было так, в файл email.txt мы записывали webdevkin@gmail.com. Ясно и понятно.
Теперь же мы хотим все настройки складывать в один файл. Создадим его: в папке admin новая папка config, а в нее положим values.json с таким содержимым
{ "email":"webdevkin@gmail.com", "phone":"8-800-123-45-67", "product":"iPhone 5x", "price":"25000", "available":"true", "discountDay":"Четверг" }
Структура простая: {"key1": "value1", "key2": "value2", ...}
Глядя на этот json-объект, можно предположить, что это настройки для лэндинга, который продает айфон за 25 тысяч, плюс айфон в наличии. Еще указаны почта и номер телефона плюс скидки по четвергам.
Идем дальше. Выше мы договорились, что дадим возможность описывать каждую настройку: заголовок, тип и дефолтные значения. Так и просится массив вида:
[{ key: 'key1', title: 'Заголовок 1', type: 'Тип 1', default: 'Значение по умолчанию 1' }, { key: 'key2', title: 'Заголовок 2', type: 'Тип 2', default: 'Значение по умолчанию 2' }, ... ]
Раз просится, то сделаем. Создадим рядом с values,json файл config.json и раскидаем настройки по такому массиву.
[{ "key": "email", "title": "Email", "type": "text", "default": "webdevkin@gmail.com" },{ "key": "phone", "title": "Телефон", "type": "text", "default": "8-800-000-00-00" },{ "key": "product", "title": "Название товара", "type": "text", "default": "Ноутбук" },{ "key": "price", "title": "Цена", "type": "number", "default": "1000" },{ "key": "available", "title": "В наличии", "type": "checkbox", "default": "true" },{ "key": "discountDay", "title": "День недели со скидкой в 10%", "type": "select", "list": ["Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота", "Воскресенье"], "default": "Воскресенье" }]
Что видим:
- Один объект - одна настройка
- key - ключ настройки, совпадает со значением в values.json
- title - заголовок, который мы увидим в интерфейсе админки
- type 4-х типов: text, number, checkbox, select
- default - значение настройки по умолчанию
- у настройки с type=select есть дополнительное поле - массив list
Почему "key", "1000" и "true", а не просто key, 1000 и true? Потому что это не массив объектов javascript, а json-массив. Поэтому все в кавычках. Дальше по тексту мы это учтем.
Так, с конфигами вроде разобрались. Надеюсь, структура нехитрая. Пора написать код, который работает с этим.
Чтение конфигов и настроек. admin.class.php
Создадим в папке admin файл admin.class.php. Там будет серверная логика работы с настройками: чтение и сохранение.
Заготовка класса
class Admin { // Пути к файлам protected $basePath; protected $configFile; protected $valuesFile; // Настройки с их значениями protected $settings = array(); // Конструктор function __construct() { $this->basePath = $_SERVER['DOCUMENT_ROOT'] . '/admin/'; $this->configFile = $this->basePath . 'config/config.json'; $this->valuesFile = $this->basePath . 'config/values.json'; $this->setSettings(); } // Инициализируем настройки private function setSettings() { // TODO Реализовать } // Возвращаем все настройки public function getSettings() { return $this->settings; } // Возвращаем значение одной настройки public function getValue($key) { // TODO Реализовать } // Сохраняем настройки в файл public function save($settings) { // TODO Реализовать } }
Пройдемся по коду. 2 поля с путями к файлам: configFile и valuesFile, в settings массив настроек. Хитрость settings в том, что там лежат и параметры настроек, и значения. То есть данные для settings мерджатся из обоих json-ов. В конструкторе мы задаем пути к файлам и считываем настройки через setSettings.
Далее по методам.
1. getSettings неинтересно, метод тупо возвращает массив.
2. getValue вытаскивает значение одной настройки.
3. save сохраняет значения настроек в values.json.
Теперь хитрее - метод setSettings.
// Инициализируем настройки private function setSettings() { $config = json_decode(file_get_contents($this->configFile), true); $values = json_decode(file_get_contents($this->valuesFile), true); foreach($config as $item) { $value = isset($values[$item['key']]) ? $values[$item['key']] : $item['default']; if ($item['type'] == 'number') { $value = (int)$value; } if ($item['type'] == 'checkbox') { $value = $value === 'true'; } array_push($this->settings, array( 'key' => $item['key'], 'title' => $item['title'], 'type' => $item['type'], 'list' => isset($item['list']) ? $item['list'] : null, 'value' => $value )); } }
Разбираемся. Сначала считываем в $config и $values содержимое соответствующих json-ов. json_decode со вторым параметром true, потому что у меня такая слабость: больше люблю работать с ассоциативными массивами, чем объектами.
Дальше бегаем по $config и складываем в $settings разные поля. 4 поля берем из $config, 1 из $values.
В начале foreach видим манипуляции с value. Это тот случай, который я обещал вспомнить. В чем смысл? В $values из json-файла попадают строки. Коду приложения не нужно каждый раз думать, годится ли цена в виде строки или же нужно именно число. Поэтому мы сделаем преобразования один раз: number и checkbox переведем в int и boolean.
Еще момент.
$value = isset($values[$item['key']]) ? $values[$item['key']] : $item['default'];
Здесь подстраховываемся. Если значение в values.json не задано, то берем дефолтное из config.json.
С чтением настроек все. getSettings уже упоминали - вернет весь массив. Это пригодится для интерфейса админки в следующем уроке.
Когда захочется вытаскивать значения настройки под одной, воспользуемся методом getValue
// Возвращаем значение одной настройки public function getValue($key) { $index = array_search($key, array_column($this->settings, 'key')); return $index !== false ? $this->settings[$index]['value'] : null; }
Странно выглядит, но на самом деле нет. Первая строка ищет объект в массиве настроек по ключу. А return возвращает value или null, если ключ неверный. Используй мы underscore.php, получилось бы изящнее, но тащить либу ради экономии трех строк кода не стоит.
На сегодня заканчиваем. Осталось проверить, работает ли чтение настроек пачкой и по одной. Напишем в index.php
include_once './admin.class.php'; echo ''; $admin = new Admin(); print_r($admin->getSettings()); print_r($admin->getValue('price')); echo '';
Запускаем, получаем полный массив настроек и значение price. Работает.
На этом пока все. В следующем уроке научимся работать с шаблонизатором Twig и выведем настройки в браузер не через суровый pre, а в нормальном виде. Будет симпатично. Почти как здесь, только лучше :-)
До встречи
Истории из жизни айти и обсуждение кода.