Делим код пополам или представление по шаблону в PHP


Зачем все это? - возмущенно спросил Артём, широко раскрыв глаза. У нас и так все прекрасно работает! Я знаю, где мне надо поменять код, чтобы новости отображались в три колонки, а не в две. А Тане я потом покажу, где поменять теги...
Если вы понимаете, какие проблемы у Тани с Артёмом, то это уже хорошо. Как некоторые догадываются Артём - PHP программист, Таня - дизайнер-верстальщик. У них есть общая проблема - файл, который формирует ленту новостей. Редактируют они его по очереди и сам черт ногу сломит в нём. А все потому, что PHP код Артёма уже давно зависит от HTML кода Татьяны и наоборот. И нет им покоя длительное время, если что-то надо поменять в этом файле. Если бы Артём с Татьяной знали о представлении по шаблону, то они бы не тратили кучу нервов и времени на столь простую функцию.

Итак, что же такое шаблонизация, шаблонизаторы, наконец, Template View, зачем они нужны, как их делать и как ими пользоваться? Все очень просто...

Представление по шаблону (Template View) или шаблонизация - это механизм, позволяющий заменять в статической HTML странице ее динамические части, то есть, простым языком говоря, в Таниной HTML страничке заменять данные, генерируемые PHP кодом Артёма. При этом Артём работает со своим файлом, в котором не видит ни одного Таниного тега, а Таня со своим. С этим механизмом Артёму и Тане очень удобно. Им больше не режет глаза чужой листинг, и их идеи в построении кода не зависят друг от друга. Таким образом, выделим два основных, почти смежных, достоинства шаблонизации:

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

Для начала маленький пример:

Listing №1 (PHP)
<?php
  date_default_timezone_set('Europe/Kiev');
  $UserMessage = 'Артём';
  if (date('md') === '0509') {
    $UserMessage .= ' С Днём Победы!';
  }
?>
<html>
<head>
<title>Пример1</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>
Привет, <?php echo $UserMessage; ?>
</body>
</html>

Поспешу вас обрадовать - перед вами простейший код, выполняющий шаблонизацию. Да, может вы и не знали, но PHP сам по себе отличный шаблонизатор. Как видно из примера, в результате выполнения сценария в статической части (шаблоне - код черного цвета) будет заменена ее динамическая часть, которую PHP сгенерирует с помощью команды echo. Это самый простой случай привожу для того, чтобы вы запомнили, что PHP - сам по себе шаблонизатор. Но как бы там ни было, такой подход не решает проблемы Артёма и Татьяны.

Разделим код на два файла: model.php и template.php. Первый отдадим Артёму, а второй Татьяне.

model.php

Listing №2 (PHP)
<?php
  date_default_timezone_set('Europe/Kiev');
  $UserMessage = 'Артём';
  if (date('md') === '0509') {
    $UserMessage .= ' С Днём Победы!';
  }
  include 'template.php';
?>

template.php

Listing №3 (PHP)
<html>
<head>
<title>Пример2</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>
Привет, <?php echo $UserMessage; ?>
</body>
</html>

Как же теперь обрадовался Артём - нет ни одного Татьяниного тега. Чистый PHP код. Татьяне тоже повезло. Хотя ее немного смущает кусочек PHP кода в теле страницы, но она не сильно обращает на это внимание, потому как знает, что в этом месте просто будет написано сообщение пользователю.

Заметьте, обычным разделением кода мы намного облегчили работу Артёма и Татьяны. Теперь статическая часть страницы, то есть интерфейс пользователя отделен от логики работы приложения. Не только Татьяна, но и любой другой человек, не владеющий программированием на PHP, но имеющий опыт в дизайне и верстке, теперь легко сможет изменить внешний вид сайта. Артем в любой момент может поменять логику работы приложения, например, заменив ее на поздравление "С Новым Годом". При этом ему не будет мозолить глаза вся эта Татьянина писанина и, пока Таня ваяет дизайн, он быстренько все поменяет и пойдет домой смотреть Футураму.

Таким образом мы отделили интерфейс от логики работы скрипта. Мы создали статический шаблон (файл template.php), в который поместили маркер PHP кода (<?php echo $UserMessage; ?>), обращающийся к скрипту для получения динамической информации - строки с приветствием пользователя. Такой метод шаблонизации называется native (родной) или прямой, потому как команда include 'template.php' фактически вставила template.php в model.php и PHP шаблонизировал все сам по себе, заменив свой вызов в шаблоне значением переменной $UserMessage.

Раз уж у нас есть такой хороший способ шаблонизации, то спрячем логику его работы в простой класс:

class.view.php

Listing №4 (PHP)
<?php
/**
 * Класс шаблонизатор
 */
class View {
  /**
   * Файл шаблона
   * @var string
   */
  private $_File;
  /**
   * Массив переменных-маркеров шаблона
   * @var array
   */
  private $_Vars;
  /**
   * Конструктор, инициализируем свойства класса
   */
  public function __construct()
  {
    $this->_File = '';
    $this->_Vars = array();
  }
  /**
   * Устанавливает файл шаблона
   * @param string $File
   */
  public function SetTemplate($File)
  {
    $this->_File = $File;
  }
  /**
   * Связывает переменные скрипта с переменными-маркерами
   * @param string $VarName
   * @param string $VarValue
   */
  public function AssignVar($VarName, $VarValue)
  {
    if ($VarName !== '') {
      $this->_Vars[$VarName] = $VarValue;
    }
  }
  /**
   * Отображает шаблон
   */
  public function Display()
  {
    if (file_exists($this->_File)) {
      extract($this->_Vars, EXTR_PREFIX_ALL, '');
      include $this->_File;
    }
  }
}
?>

Теперь попробуем его использовать. Поместим наш класс в файл class.view.php и перепишем классы model.php и template.php:


template.php

Listing №5 (PHP)
<html>
<head>
<title>Пример3</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>
Привет, <?php echo $_UserMessage; ?>
</body>
</html>


model.php

Listing №6 (PHP)
<?php
require_once 'class.view.php';
date_default_timezone_set('Europe/Kiev');
$UserMessage = 'Артём';
if (date('md') === '0509') {
  $UserMessage .= ' С Днём Победы!';
}
//Создаем объект представления интерфейса
$Veiw = new View;
//Устанавливаем шаблон представления
$Veiw->SetTemplate('template.php');
//Установка значения переменной-маркера сообщения пользователю
$Veiw->AssignVar('UserMessage', $UserMessage);
//Отображение интерфейса
$Veiw->Display();
?>


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

Давайте немного улучшим класс View. В данном случае extract использовать не эффективно по ряду причин. Это - возможные пересечения переменных, засорение области видимости, увеличение расхода памяти. Хотя, в принципе можно все оставить и так. Но все же...

class.view.php

Listing №7 (PHP)
<?php
/**
 * Класс шаблонизатор
 */
class View {
  /**
   * Файл шаблона
   * @var string
   */
  private $_File;
  /**
   * Массив переменных-маркеров шаблона
   * @var array
   */
  private $_Vars;
  /**
   * Конструктор, инициализируем свойства класса
   */
  public function __construct()
  {
    $this->_File = '';
    $this->_Vars = array();
  }
  /**
   * Устанавливает файл шаблона
   * @param string $File
   */
  public function SetTemplate($File)
  {
    $this->_File = $File;
  }
  /**
   * Связывает переменные скрипта с переменными-маркерами
   * @param string $VarName
   * @param string $VarValue
   */
  public function AssignVar($VarName, $VarValue)
  {
    if ($VarName !== '') {
      $this->_Vars[$VarName] = $VarValue;
    }
  }
  /**
   * Отображает шаблон
   */
  public function Display()
  {
    if (file_exists($this->_File)) {
      include $this->_File;
    }
  }
  /**
   * Функция доступа к элементу массива
   * переменных-маркеров шаблона
   * @param string $Varname
   * @return mixed
   */
  public function __get($VarName)
  {
    if (array_key_exists($VarName, $this->_Vars)) {
      return $this->_Vars[$VarName];
    }
    return NULL;
  }
}
?>


И немного изменим шаблон:


template.php

Listing №8 (PHP)
<html>
<head>
<title>Пример4</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>
Привет, <?php echo $this->UserMessage; ?>
</body>
</html>

Все стало лучше и проще. Заметьте, в файле model.php мы ничего не меняем - опять же, благодаря тому, что мы скрыли сложности реализации шаблонизации в классе. Скрывайте сложность - это избавит вас от многих проблем и не только при программировании на PHP.

Итак, мы познакомились с native или прямым методом шаблонизации. Его особенность, как вы понимаете, в прямом инклуде файла шаблона, при парсинге которого PHP сам заменит свои маркеры-выводы на соответствующие значения переменных.

Теперь самое время вспомнить наших Артёма с Татьяной и познакомить вас с еще одним методом шаблонизации - методом замены. Как бы мы не старались угодить Артёму с его скриптом, Татьяну все же немного напрягает присутствие чужого кода в своем милом HTML 4.01 Transitional. Дело в том, что полностью Татьяну удовлетворить нельзя, но мы можем максимально приблизить такой момент как раз с помощью метода замены. Для этого нам понадобиться что? Правильно - новый класс.

class.view.php

Listing №9 (PHP)
<?php
/**
 * Класс шаблонизатор
 */
class View {
  /**
   * Файл шаблона
   * @var string
   */
  private $_File;
  /**
   * Массив переменных маркеров шаблона
   * @var array
   */
  private $_Vars;
  /**
   * Конструктор, инициализируем свойства класса
   */
  public function __construct()
  {
    $this->_File = '';
    $this->_Vars = array();
  }
  /**
   * Устанавливает файл шаблона
   * @param string $File
   */
  public function SetTemplate($File)
  {
    $this->_File = $File;
  }
  /**
   * Связывает переменные скрипта с переменными-маркерами
   * @param string $VarName
   * @param string $VarValue
   */
  public function AssignVar($VarName, $VarValue)
  {
    if ($VarName !== '') {
      $this->_Vars[$VarName] = $VarValue;
    }
  }
  /**
   * Отображает шаблон
   */
  public function Display()
  {
    echo $this->_Prepare();
  }
  /**
   * Загружает шаблон и
   * заменяет в нем маркеры замены
   */
  private function _Prepare()
  {
    $Content = '';
    if (file_exists($this->_File)) {
      $Content = file_get_contents($this->_File);
      foreach ($this->_Vars as $VarName=>$VarValue) {
        $Content = str_replace('{' . $VarName . '}', $VarValue, $Content);
      }
    }
    return $Content;
  }
}
?>

Посмотрим теперь на наш шаблон:


template.php

Listing №10 (HTML)
<html>
<head>
<title>Пример5</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>
Привет, {UserMessage}
</body>
</html>


Да он уже и не PHP совсем! Хотя в нем и нет вообще PHP кода, в нем присутствует некий псевдокод. Но для Татьяны, дизайнера-верстальщика, это просто набор символов {UserMessage}, который довольно прост в восприятии и практически ее не отвлекает. Разве что она знает, что там, в итоге, будет имя пользователя.

Стоит заметить, что данная конструкция не разрушает никакие HTML теги и ее удобно наблюдать в WYSIWYG редакторах, в отличие от шаблонов, реализованных для native метода, которые часто могут исказить вид шаблона в "неподготовленных" к этому редакторах. Заметно, что метод замены намного удобнее для Тани. Да и Артём, как обычно, не сильно напрягается. Таким образом, мы практически полностью отделили Танин HTML код от PHP кода Артёма, а также логику вида интерфейса от логики работы приложения. Теперь, с помощью такого шаблонизатора можно написать приложение на PHP, описать маркеры в шаблонах и тогда любой дизайнер, даже понятия не имеющий, что есть PHP, сможет эффективно поработать.

Но у всего хорошего всегда есть какие-то недостатки. И этот метод не исключение. Во-первых, метод замены несколько, а порой и намного, медленнее native метода. Это естественно - мы же считываем файл с диска, ищем и заменяем в нем одни значения на другие. Во-вторых, при написании всего кода, можно теоретически совершит больше ошибок. Например, не связав переменную скрипта с переменной-маркером, мы можем получить надпись "Привет , {UserMessage}", в то время как в предыдущем подходе мы просто увидим "Привет, ". Я думаю, первое выглядит похуже. Вот такими недостатками мы можем платить за комфорт Татьяны. Поэтому, Татьяны со знаниями базовых конструкций PHP обычно ценятся подороже обычных Татьян. Но не надо так сильно останавливать внимание на недостатках. В конце концов, решающим фактором является не количество недостатков, а относительная их доля в количестве достоинств. Существует масса классов использующих, именно этот метод. Одним из самых распространенных является Smarty. Его используют миллионы сайтов и не замечают данных недостатков.

То же самое относиться и к native методу. Он не избавляет нас от PHP кода в шаблоне. Зато он намного быстрее и предоставляет более эффективное управление логикой представления, благодаря именно PHP коду внутри шаблона. Не надо стараться извлечь весь PHP код из шаблона - главное разделить логику представления и приложения. И если PHP код в шаблоне управляет логикой представления, то это есть норма и ничего более менять уже не надо. Разве что необходимость в эффективной работе дизайнеров или требования заказчика или личные предпочтения заставят вас все же использовать метод замены.

На этом можно поставить первую точку. Спешу сообщить, что все персонажи вымышленные и не надо спрашивать меня кто они такие, если вы захотите оставить комментарий к статье.
Теги:
паттерны проектирования
Добавлено: 07 Августа 2018 21:53:21 Добавил: Андрей Ковальчук Нравится 0
Добавить
Комментарии:
Нету комментариев для вывода...