САМОУЧИТЕЛЬ PHP 4

Главный модуль шаблонизатора


Основной код шаблонизатора, который и выполняет всю работу, помещен в библиотеку Template.phl. Она содержит все функции, которые могут потребоваться в шаблонах и блочных страницах. Главная функция модуля— RunUrl() — "запускает" страницу, путь к которой (относительно корневого каталога сервера) передается в параметрах. Результат работы этой функции — содержимое блока Output, порожденного страницей.

В листинге 30.14 приводится полный код шаблонизатора с комментариями.

Листинг 30.14. Модуль шаблонизатора: Template.phl

<?

// Константы, задающие некоторые значения по умолчанию

define("DefGlue"," | ");             // символ склейки по умолчанию

define("Htaccess_Name",".htaccess"); // имя .htaccess-файла

// Имена "стандартных" блоков

define("BlkTemplate","template");   // шаблон страницы

define("BlkOutput","output");       // этот блок выводится в браузер

define("BlkDefGlue","defaultglue"); // символ для "склейки" по умолчанию

// Рабочие переменные

$GLOBALS["BLOCK"]=array();      // массив тел всех блоков



$GLOBALS["BLOCK_INC"]=array();  // аналог $INC библиотекаря

$GLOBALS["CURBLOCK_URL"]=false; // URL текущего обрабатываемого файла

$GLOBALS["bSingleLine"]=0;      // обрабатываемый файл — .htaccess?

// В следующем массиве перечислены имена функций-фильтров,

// которые будут вызваны для каждого блока, когда получено его

// содержимое. Вы, возможно, захотите добавить сюда и другие

// фильтры (например, исполняющие роль простейшего макропроцессора,

// заменяющего одни тэги на другие). Формат функций:

// void FilterFunc(string $BlkName, string &$Value, string $BlkUrl)

$GLOBALS["BLOCKFILTERS"]=array(

  "_FBlkTabs",

  "_FBlkGlue"

  //*** Здесь могут располагаться имена ваших функций-фильтров.

);

// Возвращает тело блока по его имени. Регистр символов не учитывается.


function Blk($name)

{ return @$GLOBALS["BLOCK"][strtolower($name)];

}

// Добавляет указанный URL в список путей поиска. При этом путь

// автоматически преобразуется в абсолютный URL (за текущий каталог

// принимается каталог текущего обрабатываемого файла).

function Inc($url)

{ global $CURBLOCK_URL,$SCRIPT_NAME;

  $CurUrl=$CURBLOCK_URL; if(!$CurUrl) $CurUrl=$SCRIPT_NAME;

  if($url[0]!="/") $url=abs_path($url,dirname($CurUrl));

  $GLOBALS["BLOCK_INC"][]=$url;

}

// Устанавливает имя текущего блока и, возможно, его значение.

// Все данные, выведенные после вызова этой функции, будут принадлежать

// телу блока $name. Если задан параметр $value, тело сразу

// устанавливается равным $value, а весь вывод просто проигноруется.

// Это удобно для коротких однострочных блоков, особенно расположенных

// в файлах .htaccess. Из того, что было выведено программой в

// стандартный поток, будут удалены начальные и концевые пробелы,

// а также вызовутся все функции-фильтры. Окончанием вывода,

// принадлежащего указанному блоку, считается конец файла либо начало

// другого блока (то есть еще один вызов Block()).

function Block($name=false, $value=false)

{ global $BLOCK,$bSingleLine,$CURBLOCK_URL;

  // Объявляем некоторые флаги состояния

  static $Handled=false;  // в прошлый раз вывод был перехвачен

  static $CurBlock=false; // имя текущего обрабатываемого блока

  // Если имя блока задано, перевести его в нижний регистр

  if($name!==false) $name=strtolower(trim($name));

  // Вывод был перехвачен. Значит, что до этого вызова уже

  // была запущена функция Block(). Теперь блок, который

  // она обрабатывала, закончился, и его надо добавить в массив

  // блоков (или же проигнорировать этот вывод).

  if($Handled) {

    // Имя предыдущего блока было задано?

    if($CurBlock!==false) {

      // Добавляем в массив блоков.

      $BLOCK[$CurBlock]=trim(ob_get_contents());

      // Если блок однострочный (из файла .htaccess), то



      // удаляем все строки, кроме первой.

      if(@$bSingleLine)

        $BLOCK[$CurBlock]=ereg_Replace("[\r\n].*","",$BLOCK[$CurBlock]);

      // Запускаем фильтры

      _ProcessContent($CurBlock,$BLOCK[$CurBlock],$CURBLOCK_URL);

    }

    // Завершаем перехват потока вывода

    ob_end_clean();  $Handled=0;

  }

  // Если имя блока задано (а это происходит практически всегда),

  // значит, функция была вызвана нормальным образом, а не только для

  // того, чтобы завершить вывод последнего блока (см. функцию Load()).

  if($name!==false) {

    // Перехватываем поток вывода

    ob_start(); $Handled=1;

    // Тело явно не задано, значит, нужно его получить путем

    // перехвата выходного потока. Фактически, сейчас мы просто

    // говорим системе, что текущий блок — $name, и что как только

    // она встретит другой блок или конец файла, следует принять

    // выведенные данные и записать их в массив.

    if($value===false) {

      $CurBlock=$name;

    } else {

      // Тело задано явно. Записать блок в массив, но все равно

      // перехватить выходной поток (чтобы потом его проигнорировать).

      _ProcessContent($name,$value,$CURBLOCK_URL);

      $BLOCK[$name]=$value;

      $CurBlock=false;

    }

  }

}

// Загружает файл с URL $name и добавляет блоки, которые в нем

// находились, к списку существующих блоков. Параметр $name может

// задавать относительный URL, в этом случае производится его

// поиск в глобальном массиве $INC (том же самом, который использует

// библиотекарь). Если в качестве $name задано не имя файла, а имя

// каталога, то анализируется файл .htaccess, расположенный

// в этом каталоге. На момент загрузки файла текущий каталог

// изменяется на тот, в котором расположен файл.

function Load($name)

{ global $BLOCK,$bSingleLine,$CURBLOCK_URL,$BLOCK_INC;

  // Перевести все пути в $INC в абсолютные

  AbsolutizeINC();

  // Если путь относительный, ищем по $BLOCK_INC

  $fname=false;



  if($name[0]!='/') {

    // Перебираем все каталоги включения

    foreach($BLOCK_INC as $v) {

      $fname=Url2Path("$v/$name"); // Определяем имя файла

      if(file_exists($fname)) { $name="$v/$name"; break; }

    }

    // Если не нашли, $fname остается равной false

  } else {

    // Абсолютный URL — перевести его в имя файла

    $fname=Url2Path($name);

  }

  // Обрабатываем файл, имя которого вычислено по URL.

  // Сначала проверяем, существует ли такой файл.

  if($fname===false || !file_exists($fname))

    die("Couldn't open \"$name\"!");

  // Это каталог — значит, используем .htaccess

  $Single=false;

  if(@is_dir($fname)) {

    $name.="/".Htaccess_Name;

    $fname.="/".Htaccess_Name;

    $Single=1;

  }

  // Если файла до сих пор не существует (это может случиться, когда

  // мы предписали использовать .htaccess, а его в каталоге нет),

  // "мирно" выходим. Ничего страшного, если в каталоге нет .htaccess'а.

  if(!file_exists($fname)) return;

  // Запускаем файл. Для этого сначала запоминаем текущее состояние

  // и каталог, затем загружаем блоки файла (просто выполняем файл),

  // а в конце восстанавливаем состояние.

  $PrevSingle=$bSingleLine; $bSingleLine=@$Single;

  $SaveDir=getcwd(); chdir(dirname($fname));

  $SaveCBU=$CURBLOCK_URL; $CURBLOCK_URL=$name;

    // Возможно, в файле присутствуют начальные пробелы или другие

    // нежелательные символы (например, в .htaccess это может

    // быть знак комментария). Все они включатся в блок с

    // именем _PreBlockText (его вряд ли целесообразно использовать).

    Block("_PreBlockText");

    // Делаем доступными все глобальные переменные.

    foreach($GLOBALS as $k=>$v) if(!@Isset($$k)) global $$k;

    // Запускаем файл.

    include $fname;

    // Сигнализируем, что блоки закончились (достигнут конец файла).

    // При этом чаще всего будет осуществлена запись данных последнего



    // блока файла в массив.

    Block();

  chdir($SaveDir);

  $CURBLOCK_URL=$SaveCBU;

  $bSingleLine=$PrevSingle;

}

// Главная функция шаблонизатора. Обрабатывает указанный файл $url

// и возвращает тело блока Output. В выходной поток ничего не печатается

// (за исключением предупреждений, если они возникли).

function RunUrl($url)

{ global $BLOCK;

  // Собираем все блоки.

  _CollectBlocks($url);

  // Находим и запускаем главный шаблон. Мы делаем это в последнюю

  // очередь, чтобы ему были доступны все блоки, из которых состоит

  // страница. Шаблон — обычный блочный файл. В нем обязательно должен

  // присутствовать блок Output.

  $tmpl=@$BLOCK[BlkTemplate];

  if(!$tmpl) {

    die("Cannot find the template for <b>$url</b> ".

      "(have you defined <tt>".BlkTemplate."</tt> block?)");

  }

  Load($tmpl);

  // Возвращаем блок Output.

  if(!isSet($BLOCK[BlkOutput])) {

    die("No output from template <b>$tmpl</b> ".

      "(have you defined <tt>".BlkOutput."</tt> block?)");

  }

  return $BLOCK[BlkOutput];

}

// Эта функция предназначена для внутреннего использования. Она собирает

// блоки из файла, соответствующего указанному $url, в том числе и блоки

// из всех .htaccess-файлов "надкаталогов".

function _CollectBlocks($url)

{ global $BLOCK;

  $url=abs_path($url,dirname($GLOBALS["SCRIPT_NAME"]));

  // Если путь — не /, то обратиться к "надкаталогу".

  if(strlen($url)>1) _CollectBlocks(dirname($url));

  // Загрузить блоки самого файла.

  Load($url);

}

// Запускает все фильтры для блока.

function _ProcessContent($name,&$cont,$url)

{ foreach($GLOBALS["BLOCKFILTERS"] as $F)

    $F($name,$cont,$url);

}

// "Склеивание" блоков.

// Если тело блока начинается с [name], то оно не просто

// записывается в массив блоков, а "пристыковывается" к значению,



// уже там находящемуся, причем в качестве символа-соединителя

// выступает тело блока с именем name. Если строка name не задана

// (то есть указаны []), используется блок с именем DefaultGlue,

// а если этого блока нет, то соединитель по умолчанию — " | ".

function _FBlkGlue($name,&$cont,$url)

{ global $BLOCK;

  if(ereg("^\\[([^]])*]",$cont,$P)) {

    $c=substr($cont,strlen($P[0])); // тело блока после [name]

    $n=$P[1];                       // имя соединителя

    // Есть с чем "склеивать"?

    if(!empty($BLOCK[$name])) {

      $glue=@$BLOCK[$n];

      if(!Isset($glue)) $glue=@$BLOCK[BlkDefGlue];

      if(!Isset($glue)) $glue=DefGlue;

      $cont=$BLOCK[$name].$glue.$c;

    }

    // "Склеивать" нечего — просто присваиваем.

    else $cont=$c;

  }

}

// Удаление начальных символов табуляции из тела блока.

// Теперь можно выравнивать HTML-код в документах с помощью табуляции.

// Это оказывается чрезвычайно удобным, если мы используем тэги,

// например, в таком контексте:

// < ?foreach($Book as $k=>$v) {? >

//    <tr>

//      <td>< ?=$Book['name']? ></td>

//      <td>< ?=$Book['text']? ></td>

//    </tr>

// < ?}? >

function _FBlkTabs($name,&$cont,$url)

{ // используем регулярное выражение в формате PCRE, т. к. это —

  // единственный приемлемый способ решения задачи

  $cont=preg_replace("/^\t+/m","",$cont);

}

?>


Содержание раздела