За последние 24 часа нас посетили 59767 программистов и 1764 робота. Сейчас ищут 897 программистов ...

Правильная реализация шаблонизатора

Тема в разделе "Прочие вопросы по PHP", создана пользователем danies, 9 май 2015.

  1. danies

    danies Новичок

    С нами с:
    21 июл 2014
    Сообщения:
    38
    Симпатии:
    0
    Есть вот такой класс, отвечающий за все, что связано с шаблонами. Код тут кривой, но для более хорошего моих знаний пока не хватает.

    Код (PHP):
    1. <?php
    2. class Template {
    3.     //Содержимое файла
    4.     private $html;
    5.     //Директория шаблонов
    6.     private $dir;
    7.     //Директория кэша
    8.     private $dir_cache;
    9.     //Имя файла
    10.     private $name;
    11.     //Значение
    12.     private $value;
    13.     
    14.     //Что заменять
    15.     private $start_tag;
    16.     
    17.     public function __construct() {
    18.          require $_SERVER["DOCUMENT_ROOT"].'/test/system/class/Config.class.php';
    19.         $this->dir=$dir."system/html/Template/";
    20.         $this->dir_cache=$dir."system/html/Template/cache/cache.php";
    21.         
    22.         $this->start_tag=array(
    23.             "/\{% ?for (\w*)=(\w*); ?\w*([<=>]*)(\w*); ?\w*([-\+]*) ?%\}([\s\S]*){% ?\/for ?%\}/i",
    24.             
    25.             "/\{% ?if ([\w<=> ]*) ?%\}([\s\S]*)\{% ?else ?%\}([\s\S]*)\{% ?\/if ?%\}/i",
    26.             "/\{% ?if ([\w<=> ]*) ?%\}([\s\S]*)\{% ?\/if ?%\}/i",
    27.             
    28.             
    29.             "/\{% ?foreach ([\w'=>,\(\)]*) as (\w*) ?%\}([\s\S]*)\{%\/foreach%\}/",
    30.             "/\{% ?foreach ([\w'=>,\(\)]*) as (\w*) ?=> ?(\w*) ?%\}([\s\S]*)\{%\/foreach%\}/",
    31.         );
    32.     }
    33.     
    34.     
    35.     
    36.     //Выводим содержимое
    37.     public function start($values,$name) {
    38.         $this->name=$name;
    39.         $this->open();
    40.         $this->set_val($values);
    41.         
    42.         require $this->dir_cache;
    43.         
    44.         
    45.     }
    46.     
    47.     //открываем шаблон для считывания
    48.     private function open() {
    49.         if($this->html_[$this->name]==NULL) {
    50.             $this->html_[$this->name]=join('',file($this->dir.$this->name.'.tpl'));
    51.             $this->html[$this->name]=$this->html_[$this->name];
    52.         } else {
    53.             $this->html[$this->name]=$this->html_[$this->name];
    54.         }
    55.     }
    56.     
    57.     
    58.     
    59.     //Присваиваем значения
    60.     private function set_val($values) {
    61.         foreach($values as $ind=>$val) {
    62.             $this->value[$ind]=$val;
    63.         }
    64.         $this->replace();
    65.     }
    66.     
    67.     
    68.     
    69.     
    70.     //Заменяем значения
    71.     private function replace() {    
    72.         foreach($this->value as $ind=>$val) {
    73.             $key='{'.$ind.'}';    
    74.             if(is_array($val)) {
    75.             $this->html[$this->name]=str_replace($key,$this->save_mass($val),$this->html[$this->name]);
    76.             } else {
    77.                 $this->html[$this->name]=str_replace($key,$val,$this->html[$this->name]);
    78.             }
    79.             
    80.         }
    81.         
    82.         $this->for_();
    83.         $this->if_();
    84.         $this->foreach_();
    85.         
    86.         $cache=fopen($this->dir_cache,'w+');
    87.         fwrite($cache,$this->html[$this->name]);
    88.         fclose($cache);
    89.                 
    90.     }
    91.     
    92.     
    93.     
    94.     
    95.     //------Обработка--------------
    96.     private function for_() {
    97.         preg_match_all($this->start_tag[0],$this->html[$this->name],$res);
    98.         foreach($res[0] as $ind=>$str) {
    99.             $res="<?php for(\${$res[1][$ind]}={$res[2][$ind]};\${$res[1][$ind]}{$res[3][$ind]}{$res[4][$ind]};\${$res[1][$ind]}{$res[5][$ind]}) { ?>".str_replace("{{$res[1][$ind]}}","<?php echo $".$res[1][$ind]."; ?>",$res[6][$ind])."<?php } ?>";
    100.             $this->html[$this->name]=str_replace($str,$res,$this->html[$this->name]);
    101.         }
    102.         
    103.     }
    104.     
    105.     
    106.     private function if_() {
    107.         preg_match_all($this->start_tag[1],$this->html[$this->name],$res);
    108.         preg_match_all($this->start_tag[2],$this->html[$this->name],$res_);
    109.         
    110.         foreach($res[0] as $ind=>$str) {
    111.             $result="<?php if({$res[1][$ind]}) { ?>{$res[2][$ind]}<?php } else { ?>{$res[3][$ind]}<?php } ?>";
    112.             $this->html[$this->name]=str_replace($str,$result,$this->html[$this->name]);
    113.         }
    114.         
    115.         foreach($res_[0] as $ind=>$str) {
    116.             $result="<?php if({$res_[1][$ind]}) { ?>{$res_[2][$ind]}<?php } ?>";
    117.             $this->html[$this->name]=str_replace($str,$result,$this->html[$this->name]);
    118.         }    
    119.     }
    120.     
    121.     
    122.     private function foreach_() {
    123.         preg_match_all($this->start_tag[3],$this->html[$this->name],$res);
    124.         preg_match_all($this->start_tag[4],$this->html[$this->name],$res_);
    125.         
    126.         foreach($res[0] as $ind=>$str) {
    127.             $result="<?php foreach({$res[1][$ind]} as \${$res[2][$ind]}) { ?>".str_replace("{{$res[2][$ind]}}","<?php echo \${$res[2][$ind]}; ?>",$res[3][$ind])."<?php } ?>";
    128.             $this->html[$this->name]=str_replace($str,$result,$this->html[$this->name]);
    129.         }
    130.         
    131.             
    132.         foreach($res_[0] as $ind=>$str) {
    133.             $result="<?php foreach({$res_[1][$ind]} as \${$res_[2][$ind]}=>\${$res_[3][$ind]}) { ?>".str_replace("{{$res_[2][$ind]}}","<?php echo \${$res_[2][$ind]}; ?>",str_replace("{{$res_[3][$ind]}}","<?php echo \${$res_[3][$ind]}; ?>",$res_[4][$ind]))."<?php } ?>";
    134.             $this->html[$this->name]=str_replace($str,$result,$this->html[$this->name]);
    135.         }
    136.     }
    137.     
    138.     //Разбиение массива на строку
    139.     private function save_mass($mass) {
    140.         $str="array(";
    141.         foreach($mass as $ind=>$val) {
    142.             $str.="'{$ind}'=>'{$val}',";
    143.         }
    144.         $str.=")";
    145.         return $str;
    146.     }
    147. }
    По порядку: вызываем мы вот такой строчкой.
    Код (PHP):
    1. $th=new Template();
    2.     $test=array(1,5,7);
    3.     $th->start(array(
    4.              'text'=>'Главная',
    5.              'num'=>5,
    6.              'test'=>$test
    7.         ),'index'); 

    Пример шаблона:
    Код (PHP):
    1. <div>{text}</div>
    2. <br>
    3.  
    4. {%for i={num};i<8;i++%}
    5.      {i}<br>
    6. {%/for%}
    7.  
    8. {%if {num}==5%}
    9.       4
    10.  {%else%}
    11.       5
    12. {%/if%}
    13. <br><br>
    14.  
    15. {%foreach {test} as ind=>val%}
    16.      {ind}-{val}<br>
    17. {%/foreach%}
    Далее наш класс открывает шаблон с заданным именем. Если он уже открыт, берет значение из уже существующей переменной. Все это дело он присваивает переменной $html.

    Далее все наши переданные значения он кидает в массив и начинает их замену. Как он это делает:

    Первым делом заменяем все имена переменных из шаблона на значения из массива(ищем по ключу), если же имя переменной - массив, он превращает ее в строку функцией save_mass для корректности работа скомпилированного позже php кода.

    Далее по очереди заменяем регулярками for, if и foreach.

    После того, как текст шаблона полностью редактирован и готов к выводу, весь php код нужно скомпилировать. Тут и проблема. Как я это сделал: Просто создал файл cache.php, в котрый я помещаю этот самый код, а потом просто подключаю этот самый файл к странице, тем самым выводя результат. Но что будет, если, скажем, одновременно человек 50 будут загружать страницу? Серверу просто не хватит времени на запись и вывод из файла кода у каждого пользователя, и у одного пользователя будет выводиться то, что должно вывестись у другого.

    Как правильно можно откомпилировать весь php код в шаблоне? На ум приходит только eval, но тогда весь остальной код придется помещать в echo, чтобы не было такого: eval("}")

    PHP, JavaScript, SQL и другой код пишите внутри тегов
    Код ( (Unknown Language)):
    1. [b]php][/b]Тут код[b][/[/b][b]code][/b][/color]
     
  2. [vs]

    [vs] Суперстар
    Команда форума Модератор

    С нами с:
    27 сен 2007
    Сообщения:
    10.559
    Симпатии:
    632
    Как правило заводится директория cache и в неё складываются скомпилированные файлы со случайными именами
     
  3. danies

    danies Новичок

    С нами с:
    21 июл 2014
    Сообщения:
    38
    Симпатии:
    0
    Я думаю, что есть более элегантные методы)
     
  4. [vs]

    [vs] Суперстар
    Команда форума Модератор

    С нами с:
    27 сен 2007
    Сообщения:
    10.559
    Симпатии:
    632
    Смысл еще в том, что эти файлы можно использовать повторно, если страница не менялась, без перекомпиляции
     
  5. danies

    danies Новичок

    С нами с:
    21 июл 2014
    Сообщения:
    38
    Симпатии:
    0
    Большинство шаблонов выводят динамическую информацию из базы, а извлекается она не в самом шаблоне, а при передаче в него значений, так что все равно придется менять значения
     
  6. [vs]

    [vs] Суперстар
    Команда форума Модератор

    С нами с:
    27 сен 2007
    Сообщения:
    10.559
    Симпатии:
    632
    В таком случае, страничка разбивается на множество мелких кэширующихся шаблонов. Часть из них может жить без изменений довольно долго. Просто подели количество изменений на страницах в день на количество просмотров страниц и поймешь, насколько это круто.

    Добавлено спустя 1 минуту 32 секунды:
    Тут вот есть достаточно содержательный топик viewtopic.php?f=27&t=11926
     
  7. danies

    danies Новичок

    С нами с:
    21 июл 2014
    Сообщения:
    38
    Симпатии:
    0
    Идею понял, спасибо

    Добавлено спустя 27 минут:
    Остановился пока что на таком
    Код (Text):
    1.  
    2. class Template {
    3.     //Директория шаблонов
    4.     private $dir;
    5.     //Имя файла
    6.     private $name;
    7.     //Значение
    8.     private $value;
    9.    
    10.    
    11.     public function __construct() {
    12.          require $_SERVER["DOCUMENT_ROOT"].'/test/system/class/Config.class.php';
    13.         $this->dir=$dir."system/html/Template/";
    14.     }
    15.    
    16.    
    17.    
    18.     //Выводим содержимое
    19.     public function start($values,$name) {
    20.         $this->name=$name;
    21.         $this->set_val($values);
    22.         $this->open();
    23.     }
    24.    
    25.     //открываем шаблон для считывания
    26.     private function open() {
    27.         foreach($this->value as $ind=>$val) {
    28.             $$ind=$val;
    29.         }
    30.         require $this->dir.$this->name.'.php';
    31.     }
    32.    
    33.    
    34.    
    35.     //Присваиваем значения
    36.     private function set_val($values) {
    37.         foreach($values as $ind=>$val) {
    38.             $this->value[$ind]=$val;
    39.         }
    40.     }
    41. }
    Шаблоны будут иметь вид
    Код (Text):
    1. <div><? echo $text ?></div>
    2. <br>
    3. <div><? echo $num ?></div>
    4. <br>
    5. <? foreach($test as $t) {
    6.     echo $t;
    7.  } ?>
    Вызов так же.

    И времени гораздо меньше на обработку
     
  8. rodent90

    rodent90 Новичок

    С нами с:
    26 мар 2015
    Сообщения:
    533
    Симпатии:
    37
    Круто! Ты сделал из php шаблонизатора еще один php шаблонизатор ;) Поздравляю!!!
     
  9. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.128
    Симпатии:
    1.248
    Адрес:
    там-сям
    rodent90, нет ничего дурного в том чтобы использовать родные PHP файлы в качестве шаблонов. надеюсь цель автора цель в изолировании представления от всего прочего и она выполняется. если у кого-то цель создать новый "более лучший" язык разметки — вот тут стоило бы насторожиться )))

    примеры шаблонизаторов с нативными пхп-шными шаблонами:
    http://platesphp.com/ ,
    http://symfony.com/doc/current/components/templating/index.html
    да и ларавелевский blade почти ничего не изобретает, все выражения копирутся дословно, функции никак не ограничиваются. в своем шаблонизаторе я поддержал такой подход )))

    danies, вместо
    <? echo $text ?>
    пиши
    <?= $text ?>

    короткие теги считаются плохим стилем, в отличие от ехо-тега. кроме того, мне кажется неудобным множество вызовов $o->set_val(), вместо того чтобы один раз закинуть всё необходимое при открытии шаблона. лично для меня вот так в самый раз:
    Код (PHP):
    1. $view->render('mytemplate', compact('alfa', 'beta', 'gama'));
     
  10. rodent90

    rodent90 Новичок

    С нами с:
    26 мар 2015
    Сообщения:
    533
    Симпатии:
    37
    artoodetoo. Мне не нравится такой подход.
    Не один из перечисленных мне не понравился тем, что у них вперемешку все.
    Вообще не по теме.
    Код (PHP):
    1. {{ isset($name) ? $name : 'Default' }}
    :D - убожество проще тогда вообще не ставить такой шаблонизатор.
     
  11. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.128
    Симпатии:
    1.248
    Адрес:
    там-сям
    ты о чем вообще? ))) если о блейде, то там написано: "вместо вот этого убожества вы можете написать {{$x or 'Default'}} "
    но ты видишь только то, что хочешь видеть, кажется. )))

    лучше расскажи какой подход тебе НЕ кажется убожеством.
     
  12. rodent90

    rodent90 Новичок

    С нами с:
    26 мар 2015
    Сообщения:
    533
    Симпатии:
    37
    Я перепробовал массу шаблонизаторов и мне просто не понравилось это все, решил остаться на просто php =)
    Код (PHP):
    1. <?=func()?>
    2. <?=$var?>
    Я все обработки делаю до вывода, а в вывод отправляю либо функцию либо переменную.