Задача довольно тривиальная, создать форму которая позволяет изменить значения некоторых параметров и занести изменения в существующий файл. Но когда часто сталкиваешься с однотипной задачей хочется найти какое-то более менее универсальное решение чтобы не писать каждый раз унылый велосипед.
К сожалению гугл не дал простого и функционального решения, не знаю может гугл на меня за что то зол:). Возможно есть какое-то решение но подходящей мне я не нашел. Прошу сильно не ругаться, если таковой всё-таки существует. Задумался, а какой бы функционал меня бы устроил.
Во первых это вывод на редактирование только тех параметров которые нужны, во вторых это чтобы некоторые параметры выводились как 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 не была актуальная версия класса.
Комментарии (22)
MetaDone
16.11.2016 13:06http://symfony.com/doc/current/components/yaml.html#writing-yaml-files
гибко, читаемо, не костыльно.
ellrion
16.11.2016 13:11+1Идея может и неплохая, но код… Приведите код к psr-2. Смените кодировку на utf-8. Добавьте тесты. Замените var на нормальную область видимости. Вынесите шаблон и JS код отдельно от класса.
Vencendor
16.11.2016 13:16-3Спасибо учту. Пока нет необходимости разделять на файлы. Суть в том чтобы инструмент был максимально компактным и состоял из одного файла.
ellrion
16.11.2016 13:38+1максимально компактный но зависимость от jquery? И как вы сами вообще умудряетесь там всё править в этой мешанине. Ну уже хотя бы как то разделили код метода showFormна части по внутренним темплейтам в heredoc. или вообще зачем там класс тогда, сделали бы просто скрипт, тоже было бы в одном файле но чище. Ну или phar есть.
На самом деле я удивляюсь вашей "смелости". Такое на хабр выкладывать… Идея спорная всё же. А исполнение хуже некуда.
vlreshet
16.11.2016 14:14Уже не то время когда компактность ценится. Разработчику гораздо проще выполнить одну команду в композере, чем качать ваш файлик, потом думать куда его приткнуть, потом подключить его ещё где-то… И как заметил комментатор выше — jquery тут излишний.
vlreshet
16.11.2016 13:15Зачём вообще нужен визуальной редактор для такой вещи как конфиг? Это же не та вещь которая изменяется каждый день, и без удобств ну совсем никак. Плюс это потенциальная уязвимость.
Ualde
16.11.2016 13:15Самое удивительное, что часто конфигурация имеет больше 2-х уровней вложенности, и тут все перестает работать…
Lure_of_Chaos
16.11.2016 13:52Плюс, сильно зависит от форматирования и правильности комментариев в конфиге
oxidmod
16.11.2016 13:37ИМХО, не стоит давать мышкой тыкать в конфигах, а то натыкают))
Vencendor
16.11.2016 13:53в основных конфигах точно. Но вот всякие параметры типа количество объектов на странице, емайл, и прочее можно вывести для сохранности в отдельный файлик.
Ну и на худой конец класс сразу же создает бэкап который позволяет вернуться к первоначальной версии.Lure_of_Chaos
16.11.2016 14:01Я бы вообще для визуального редактирования, если уж очень понадобилось, использовал бы какой-нибудь свой формат, с экспортом и опционально импортом, а конфиги напрямую редактировать не давал бы — только ручками (по фтп, например)
zenn
16.11.2016 13:52+2Извините, но что это за убожество и как оно попало на habr? Автор, вы в php 2 недели отроду? Я пожалуй впервые вижу столь извращенный велосипед там, где он совсем не нужен и там, где есть отлично документированные нативные функции php. Отбросим следование стандартам, psr, best practice и другие концепции, давайте по факту.
1. Логика чтения и записи в файл конфигов. Я или очень сильно отстал от жизни, или чего-то не знаю, но зачем делать вот это:
public function getAtributes() { $conf_str=file_get_contents($this->file_name); foreach($this->date as $n=>$cf) { if(preg_match("#".$n."[^\r\n]*//([^\n\r]+)[\n\r]#",$conf_str,$m)) $this->atribute[$n]=trim($m[1]); } }
Что вам мешает иметь конфиги вида:
<?php return [ 'param1' => [ 'param2' => 'value' ] ];
и делать прямой инклюд данного конфиг-файла как то так:
.... if (!file_exists($path)) { return; } $configArray = include($path); // look at ex#5 http://php.net/manual/en/function.include.php if (!is_array($configArray)) { return; }
и вы получите тот же самый массив конфига без извращений через preg_match*, притом что ниже в конструкторе вы так делаете сами (вопрос — а на*рена этот конструктор там вообще?)
public function __construct($file_name) { // .... $this->date = require $this->file_name; // ..... }
Поехали дальше. Зачем вы делаете замену значений по ключу в строке?
public function safe(){ $conf_str=var_export($this->date,true); //$atr=$model->getAtribute(); foreach($this->atribute as $n=>$v) { $conf_str=preg_replace("#([^\n\r]*".$n."[^\n\r]*)[\n\r]#is","\\1//".$v."\n",$conf_str); } file_put_contents($this->file_name,"<? \n return ".$conf_str." \n ?>"); }
выглядит так, будто вы специально пытаетесь выстрелить себе в ногу или сломать ее о свой кривой велосипед. Почему вы не работаете со своим же массивом $this->data не обновляя в нем значения переменных из входящего POST а позже не конвертируете финальный результат в str (var_export)? Какую цель вы преследуете, заменяя значения через preg_replace в сконвертированной строке?
2. Смешались в кучу кони-люди. Зачем вы используете синтаксис html/css в теле функций на echo? Я видал всякое, видал echo внутри php конструкций, видал echo и return'ы внутри функций, но чтобы в теле класса содержимое метода представляли вот так:
<?php class Configer { function showForm(){ ?> <style> #configForm span{display:inline-block; width:150px; } }
я вижу впервые… не нужно так.ellrion
16.11.2016 14:08+1танцы с preg_match и preg_replace там я так понял из-за комментариев. Другое дело что благодаря коду это не очевидно)
zenn
16.11.2016 14:21Да, я это уже понял позже когда более пристально глянул в «код», если его так можно назвать. Стоит ли оно того конечно вопрос (выходит что конфиг инклюдится, а позже считывается, а при сохранении замены ведутся перебором цикла, и тут шаг в сторону будет равен выстрелу в ногу).
Lure_of_Chaos
16.11.2016 14:13Ну вообще код, представленный в топике, попадает в раздел «не нужно, но было бы приятно прихерачить сюда гуй — гуяк гуяк и в продакшн через 2 часа», и никогда не должен выкладываться на хабр.
Ибо сюда заходят люди, которые хотят прочитать что-то действительно новое и интересное.
А подобные топики нещадно заминусовываются вместе с кармой автора.
У самого таких поделок вагон и маленькая тележка, но они редко попадают даже на личный гитхаб, ибо смысл?Rottenwood
16.11.2016 16:32+1Возможно смысл в том, чтобы получить критику от сообщества. Например, zenn потратил время на анализ представленного автором, а значит конвертация кармы в опыт вполне может быть оправданна.
SerafimArts
16.11.2016 19:12+2У автора под рукой интернет и если бы он потрудился изучить статьи других авторов — не раз бы наткнулся на ссылку: http://getjump.me/ru-php-the-right-way/ и мог хотя бы попытаться следовать, пусть не всем, но хотя бы большинству рекомендаций. Моё мнение, если бы автор стремился к критике сообщества — он бы мог её получить и без хабрасуицидов.
P.S. В нашем же случае — автор просто положил большой болт и понадеялся на непонятно что. Да, можно оценивать его работу, как действительно какую-то работу и сказать ему «спасибо» за это. Ну вклад в сообщество и всё такое…
Но тут немного иная ситуация: Подобная задача решается намного проще, быстрее и качественнее, так что такой «вклад» не только не нужен, сколь вреден. И вреден не только как пример «как не надо писать», но и тем, что он может навредить тому, кто решится его использовать, начиная с безопасности, заканчивая повреждением исходных файлов конфигураций.
SerafimArts
PSR? «array()»? открывающий «<?»? Composer? И в качестве добивающего «var» в объявлении полей класса.
Шёл 2016ый год…
SerafimArts
P.S. Я уж не говорю про смешение логики и представление — это само собой очевидно и говорит лишь о небольшом опыте.
Но использование var… Это же PHP начала 2000х годов, когда в мире царствовала версия 4. Это даже если учитывать небольшой опыт — надо очень сильно постараться, чтобы найти какой-то «учебник», где такое написано, примерно как ПК с Pentium 3 на борту. Просто раритет и подрастающее поколение такое даже в глаза не видело нынче.
vlreshet
Да тут весь код такой что при чтении хочется забиться в уголок обнять колени и рыдать