Pull to refresh

Класс для редактирования конфигурационных файлов

Reading time5 min
Views3.1K
В любом веб проекте используются конфигурационные файлы. Чаще всего они редактируются на стадии разработки или переносе проекта а потом надолго остаются нетронутыми. Но бывает что конфиг разрастается и в нем появляются параметры которые приходится изменять чаще.
Задача довольно тривиальная, создать форму которая позволяет изменить значения некоторых параметров и занести изменения в существующий файл. Но когда часто сталкиваешься с однотипной задачей хочется найти какое-то более менее универсальное решение чтобы не писать каждый раз унылый велосипед.

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

Во первых это вывод на редактирование только тех параметров которые нужны, во вторых это чтобы некоторые параметры выводились как checkbox-ы, radio кнопки если это соответствует их логике ну и не мешало-бы чтобы параметры имели красивое название при выводе.

Если наглядно то чтобы из этого:


Можно было получить это:


При этом чтобы все было просто и надежно.

И так приступим. Конфигурационные файлы часто имеют вид файла php с определениями разных констант переменных и прочего. Такой формат сложно както уверено редактировать, гораздо проще и не менее распространённый формат, когда весь конфигурационный файл представляет из себя массив данных. Вот с этим можно что-то придумать. Работать будем с максимум двумерными массивами, для большей глубины не думаю, что решение будет уже универсальным. К тому же конфиг может содержать такие сложные данные, однако их редактирование будет невозможно.

Данные второго уровня выводятся в отдельном блоке fieldset который позволяет визуально разграничивать разные наборы параметров, также это блок можно сворачивать кликом мыши.

Для того чтобы не изменяя данные массива иметь возможность его как-то описать будем использовать обычные php комментарии. Комментарии которые будут на одной строке с значением будут описывать его и создавать определенное поведение при созданий формы. Во первых это вывод более содержательного названия для полей. Ищем регулярным выражением комментарии на той же строке что и определение поля.

// Parse atributes of values from config file 
    public function getAtributes() {
        $conf_str = file_get_contents($this->file_name);
        foreach ($this->data as $n => $cf) {
            // First level of array 
            if (preg_match("#" . $n . "[^\r\n]*//([^\n\r]+)[\n\r]#", $conf_str, $m)) {
                $this->atribute[$n] = trim($m[1]);
            }
            if (is_array($cf)) {
                //second level 
                foreach ($cf as $ns => $vs) {
                    if (preg_match("#" . $n . ".*" . $ns . "[^\r\n]*=>[^\r\n]*//([^\n\r]+)[\n\r]#isU", $conf_str, $m)) {
                        $this->atribute[$n . "~" . $ns] = trim($m[1]);
                    }
                }
            }
        }
    }

сохраняем все полученные данные и будем выводить в форму только те что имеют описание.
Дальше не плохо было бы в этом описании иметь возможность вводить какие-то ключи, которые будут влиять на тип отображаемого элемента формы. Например задавать допустимые для текущего параметра значения, формат был выбран такой [options| знач1|…|значn].

Парсим полученные комментарии, опять же регуляркой чтобы выудить из них список значений. Решил не добавлять возможность указывать какой именно тип инпута должен быть выведен, все это логично исходит из предложенных значений. Если значений только 2 и они обе булевского типа то можно выводить checkbox, если значений больше, то выводим радиокнопки а если уж совсем много то рисуем select. Для полей которые не имеют каких-то опции выводим универсальный текстовый input.

    // Parsing options from values atribute 
    public function parseAtribute($atribute) {
        $flags = array();

        $flags['title'] = $atribute;
        //can't delete this value
        if (strpos($atribute, "[static]") !== false) {
            $flags['static'] = true;
        } else {
            $flags['static'] = false;
        }
        //can add values in this sub array
        if (strpos($atribute, "[dinamic]") !== false) {
            $flags['dinamic'] = true;
        }
        //toggled block by default
        if (strpos($atribute, "[hidden]") !== false) {
            $flags['hidden'] = true;
        } else {
            $flags['hidden'] = false;
        }
        //parsing options that can by values for curent input
        //select type of input 
        if (strpos($atribute, "[options") !== false) {
            preg_match("#\[options\|(.+)+\]#", $atribute, $options);

            $options = explode("|", $options[1]);
            if (sizeof($options) == 2) {
                // if have 2 options and both its from boolean type 
                $checkbox = true;
                foreach ($options as $od) {
                    if (!in_array($od, $this->booleanValues(), true)) {
                        $checkbox = false;
                    }
                }
                if ($checkbox) {
                    $flags['options']['type'] = "checkbox";
                } else {
                    $flags['options']['type'] = "radio";
                }
            } elseif (sizeof($options) < $this->optForSelect) {
                $flags['options']['type'] = "radio";
            } else {
                $flags['options']['type'] = "select";
            }
            $flags['options']['data'] = $options;
            // parse labels for values and clear data
            foreach ($options as $n => $v) {
                if (preg_match("#(.*)\((.*)\)#", $v, $m)) {
                    $flags['options']['data'][$n] = trim($m[1]);
                    $flags['options']['labels'][$n] = $m[2];
                }
            }
        } else {
            $flags['options'] = false;
        }
        // clear title of input 
        $flags['title'] = trim(preg_replace("#\[[^\[\]]*\]#", "", $flags['title']));

        return $flags;
    }

В вариантах значений можно указывать красивые названия в обычных скобках чтобы при созданий списков или кнопок выводить их а не безжизненные, сухие цифры типа 0,1,2. К сожалению такой формат не дает возможности делать варианты значений содержащие символы скобок "(" и ")".

Пришлось много повозиться с обработкой checkbox- ов, от них передается значение либо «on» либо вообще пустое. Приходится сравнивать со списком собранных атрибутов и обрабатывать пустое значение как булевский ноль. Свойства класса trueValues и falseValues содержат значения, которые могут быть интерпретированы в двоичном смысле 0-1, true-false, yes-no.

	//Convert values to 0 or 1 
	public function toBool( $val ){
		if( in_array($val, $this->trueValues) )
			return 1;
		if( in_array($val, $this->falseValues) )
			return 0;
		
		return false;
	}

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

Ну а когда все данные из конфига выужены можно рисовать форму. Пришлось ввести в класс и вывод html верстки но зато все решение состоит из одного файла который полностью решает постовленую задачу. Jquery используемый для некоторых манипуляций элементами формы, подключается на лету если не был подключен до этого. Например filedset можно свернуть что позволяет легче ориентироваться на форме большого размера и найти нужный параметр.
Для удобства некоторые блоки параметров можно указать изначально свернутыми опцией [hidden].

Для данных второго уровня можно добавить возможность создавать дополнительные поля, имя поля будет сгенерировано автоматически, а вот значение можно ввести свое. Если для каких-то задач это потребуется, то добавьте ключ [dinamic] в определении родительского поля. Если все же некоторые поля внутри этого должны оставаться нетронутыми, то укажите для них ключ [static].

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

<?php
include "../configer.php";
$cf = new Configer("settings.php");
$cf->showForm();

Ну и чтобы в вашем файле settings были какието комментарий с ключиками.

Скачать класс можно на https://github.com/vencendor/Configer.

Буду рад полезным замечаниям.

UPD: Рекомендации учтены, к сожалению на момент появления статьи на github.com не была актуальная версия класса.
Tags:
Hubs:
-5
Comments22

Articles