13 636
правок
Изменения
Нет описания правки
* Лицензия: GNU GPL версии 3 или новее* Полный набор исходников здесь: http://yourcmc.ru/git/vitalif/VMXTemplate** PHP-версия (PHP >== Идеи ==5.4): [http://yourcmc.ru/git/vitalif/VMXTemplate/raw/master/template.php template.php] и [http://yourcmc.ru/git/vitalif/VMXTemplate/raw/master/template.parser.php template.parser.php].** Новая Perl-версия: [http://yourcmc.ru/git/vitalif/VMXTemplate/raw/master/VMXTemplate.pm VMXTemplate.pm] и все его [http://yourcmc.ru/git/tree/VMXTemplate.git/master/VMXTemplate подмодули VMXTemplate/*.pm].** Старая Perl-версия: [http://yourcmc.ru/git/vitalif/VMXTemplate/raw/master/VMX%2FTemplate.pm VMX/Template.pm] и [http://yourcmc.ru/git/vitalif/VMXTemplate/raw/master/VMX%2FCommon.pm VMX/Common.pm].** Исходный код грамматики PHP-версии (LALR(1) [http://yourcmc.ru/git/vitalif/lime LIME]): [http://yourcmc.ru/git/vitalif/VMXTemplate/raw/master/template.lime template.lime].** Исходный код грамматики Perl-версии (LALR(1) {{CPAN|Parse::Yapp}}): [http://yourcmc.ru/git/vitalif/VMXTemplate/raw/master/template.yp template.yp] и [http://yourcmc.ru/git/vitalif/VMXTemplate/raw/master/template.skel.pm template.skel.pm].** Исходный код голой грамматики (LALR(1) yacc): [http://yourcmc.ru/git/vitalif/VMXTemplate/raw/master/template.y template.y].** В Git-репозитории можно найти полную историю разработки, начиная с самых старых phpbb-подобный версий.* Простые настройки для подсветки синтаксиса шаблонов в [http://www.midnight-commander.org/ Midnight Commander]'а: [{{SVN|vitalif/trunk/scripts/tpl.syntax|markup}} tpl.syntax]. Чтобы подсветка нормально выглядела, к tpl.syntax в начало надо дописать html.syntax из стандартного комплекта поставки mc.
== Реализация Использование ==
<pre><!code-- BEGIN block --></prephp>require_once 'template.php';
# Конструктор$template = new VMXTemplate( 'root' => '.', # директория с шаблонами 'cache_dir' => undef, # директория для кэширования компилированного кода шаблонов 'reload' => 2, # если 0, то шаблоны не будут перечитываться с диска, и вызовов stat() происходить не будет # если >0, то шаблоны будут перечитываться с диска не чаще чем раз в reload секунд 'print_error' => 1, # если TRUE, ошибки компиляции попадают в вывод шаблона 'log_error' => 1, # если TRUE, ошибки компиляции печатаются на STDERR 'raise_error' => 0, # если TRUE, при ошибке компиляции вызывается die() 'use_utf8' => undef, # если TRUE, использовать "use utf8" на шаблонах 'begin_code' => '<pre!--', # маркер начала директивы кода 'end_code' =>'-->', # маркер конца директивы кода 'begin_subst' => '{function', # маркер начала подстановки выражения 'end_subst' => '}', # маркер конца подстановки выражения 'eat_code_line' => 1, # (blockпохоже на TT CHOMP) если TRUE, съедать "лишний" перевод строки, если в строке только директива кода (begin_code.key.end_code) 'no_code_subst' => 1, # если TRUE, выполнять директивы кода (begin_code..end_code), но игнорировать их результат 'compiletime_functions' => # дополнительные компилируемые функции { 'func' => sub {} }, # хеш вида имя функции (в шаблонах) => coderef, # которому передаются скомпилированные выражения всех аргументов и первым - парсер (объект VMXTemplate::Parser) 'filters' => [ sub {}, .. ], # фильтры для запуска на выводе каждого внешнего шаблона (фильтр - функция, модифицирующая $_[0]) 'strip_space' => 0, # если TRUE, удалять пробелы и табы из начала и конца всех строк вывода 'auto_escape' => '', # функция авто-экранирования, например "abcs"(для HTML-безопасного режима)}</pre>);
<pre>
<!-- SET blocktitle --> Статистика<!-- END --><!-- SET headscripts --> <script language="javascript" type="text/javascript" src="{DOMAIN}/tpldata/jquery.key min.js"></script><!-- END --><!-- INCLUDE "admin_header.tpl" --><!-- IF NOT srcid --> <p>Добро пожаловать в простую OLAPообразную статистику.Выберите источник данных:</p> <form action="?" method="GET"> <select style="width:100px" name="datasource"> <!-- FOR s = sources --> <option value="{s s.id}">{s s.name}</option> <!-- END --> </nowikiselect> <input type="submit" value=" Продолжить " /> </form><!-- SET blockELSEIF srcid == "test" || sources[srcid].key mode = выражение = 'test' --> <p>Тестовый режим.</p><!-- END --><!-- INCLUDE "admin_footer.tpl" -->
</pre>
== Функции = Маркеры ===
* Выражения состоят из переменных, операторов и вызовов функций и методов объектов* Синтаксис обращений к переменным JS-подобный: <tt>hash.key</tt>, <tt>array[0]</tt>, <tt>hash['key']</tt>* Имена переменных '''регистрозависимы''', имена встроенных функций и названия директив (BEGIN, END и т. п.) — '''регистронезависимы'''* Никаких ошибок при обращениях к необъявленным переменным не происходит* Компилируемые функции: <tt>function(arg1, arg2, ...)</tt> или <tt>function single_arg</tt> (через пробел с одним аргументом)* Функции, определённые через FUNCTION: <tt>fn_name('arg' => 'value', 'arg2' => 'value2', ...)</tt> или <tt>exec('fn_name', { 'arg' => 'value', 'arg2' => 'value2', ... })</tt>* Включение других шаблонов: <tt>INCLUDE 'template.tpl'</tt>, <tt>INCLUDE('template.tpl', { 'arg' => 'value', ... })</tt>* Выполнение функции из другого шаблона: <tt>exec_from('template.tpl', 'fn_name', { 'arg' => 'value', 'arg2' => 'value2', ... })</tt>* Методы объектов: <tt>var.method(arg1, arg2, ...)</tt> === Операторы === Все операторы, кроме сравнений, левоассоциативны. Сравнения — не ассоциативны. <tab sep="bar" class="wikitable">a .. b | Конкатенацияa <html>||</html> b, a OR b | Логическое ИЛИ. JS- или Perl-подобное — возвращает первое истинное значение.a XOR b | XOR — логическое исключающее ИЛИa && b, a AND b | Логическое Иa & b | Побитовое Иa == b, a != b, a < b, a > b, a <= b, a >= b | Операторы сравненияa+b, a-b, a*b, a/b, a%b | Сложение, вычитание, умножение, деление, взятие остатка от деления!a, NOT a | Логическое НЕ(exp) | Выражение в скобках{ 'a' => 'b', … } | Создание хешрефа (Perl), ассоциативного массива (PHP)</tab> === Директивы === <tab sep="bar" class="wikitable"><tt><!--# Комментарий --></tt> | Комментарий<tt><!-- FOR item = array --></tt><br />…код…<br /><tt><!-- END --></tt> | Цикл. Вместо FOR можно использовать слово FOREACH. Внутри цикла можно обратиться к счётчику через <tt>{item_index}</tt><tt><!-- IF выражение --></tt><br />…код… | Если (выражение)<tt><!-- ELSEIF выражение --></tt><br />…код… | Иначе если (выражение)<tt><!-- ELSE --></tt><br />…код… | Иначе<tt><!-- END --></tt> | Конец если / цикла / присваивания<tt><!-- SET var = выражение --></tt> | Присваивание переменной var результата выполнения выражения<tt><!-- SET var --></tt><br />…код…<br /><tt><!-- END --></tt> | Присваивание переменной var результата выполнения кода<tt><!-- FUNCTION name (arg1, arg2) = выражение --></tt> | Определение функции шаблона как результата выполнения выражения<tt><!-- FUNCTION name (arg1, arg2) --></tt><br />…код…<br /><tt><!-- END --></tt> | Определение функции / «блока» шаблона. Вместо FUNCTION можно использовать также слова BLOCK или MACRO</tab> === Функции === Синтаксис вызова функций:* <tt>ФУНКЦИЯ(АРГУМЕНТ, АРГУМЕНТ, ...)</tt>* <tt>ФУНКЦИЯ <пробел> ОДИН_АРГУМЕНТ</tt> Существующие функции перечислены ниже. Через «=» в подзаголовках указываются синонимы функций. Расширяемость в области функций: ;Run-time функции: В качестве функции можно использовать метод переданного в хеше данных объекта. В «функцию» можно вынести и блок кода из шаблона — см. [[#Блоки]]. Оно хорошо кэшируется.;Compile-time функции: При создании объекта шаблона можно передать параметр <tt>compiletime_functions</tt>, равный хешу, в котором ключи — имена дополнительных функций, а значения — любые coderef’ы (Perl) или callable (PHP). Эти функции вызываются в контексте объекта шаблона с параметрами, равными '''коду для вычисления соответствующего аргумента''', и должны возвращать '''код для вычисления результата'''. То есть, они выполняются на этапе компиляции. {{note}} Первое, что обычно нужно — это S(), H(), T(), Q(), I(), то есть «фильтры» для различных преобразований строки: * S() — это htmlspecialchars(), экранирует HTML/XML-спецсимволы в строках.* H() — удаляет все HTML-теги, кроме «безопасных».* T() — удаляет все HTML-теги.* Q() — это addslashes(), экранирует строки для использования, например, в JS.* I() — преобразует значение к целому числу. ==== HTML-безопасный режим ==== Если вы хотите использовать «HTML-безопасный» режим с автоматическим экранированием — установите опцию '''auto_escape''' равной, например, как раз «s». Смысл режима в том, чтобы:* Не экранировать все значения руками* Случайно не забыть что-то экранировать, экранируя это руками Работает так: если какое-то подставляемое значение не экранировано вами явно через одну из функций вроде перечисленных выше (s/t/h/i и т. п.), то оно будет экранировано функцией, заданной в auto_escape. Получается «авто-защита» от атак типа XSS. Значения, которые надо подставить «как есть», нужно предварить вызовом функции RAW: {raw value}. Тогда значение auto_escape’ом экранировано не будет. На заметку: для удобства функции JSON, QUOTE, SQL_QUOTE и REQUOTE считаются «безопасными», хотя таковыми, строго говоря, не являются. Однако используются они обычно внутри JS-кода, поэтому лучше их вывод не трогать. === Числа, логические операции === ==== LOG ==== Логарифм. ==== EVEN, ODD ====
Истина в случае, если аргумент чётный или нечётный соответственно.
==== INT, ADD, MUL, DIV, MOD =I=INTVAL ====
Преобразование к целому числу и арифметические операции.
=== EQ, = SEQ, GT, LT, GE, LESNE, SGT, SLT, SGE, SLE ====
=== CONCAT= NEQ, JOINNNE, SPLITNGT, COUNT NLT, NGE, NLE ====
==== LC=LOWER=LOWERCASE, UC=UPPER=UPPERCASE ====
Нижний и верхний регистр.
=== L=TRANSLATELCFIRST, LZUCFIRST =TRANSLATE_NULL === Преобразование первого символа строки в нижний и верхний регистр соответственно. ==== Q=QUOTE=ADDSLASHES, SQ=SQL_QUOTE, REQUOTE=RE_QUOTE=PREG_QUOTE ==== * Экранирование символов " ' \ и перевода строки бэкслэшем — quote(строка).* Экранирование символа " удвоением — sql_quote(строка). (актуально также для [[rupedia:CSV|CSV]])* Экранирование символов, являющихся специальными в регулярных выражениях — re_quote(строка). (см. [http://perldoc.perl.org/perlre.html perldoc perlre]). ==== URI_QUOTE=URIQUOTE=URLENCODE ==== URL-кодирование строки ({{CPAN|URI::Escape}} в Perl и [http://php.net/manual/en/function.urlencode.php urlencode()] в PHP). ==== REPLACE, STR_REPLACE ==== * Замена Perl- (соответственно PCRE- в PHP-версии) регулярного выражения в строке — replace(RegExp, замена, строка).* Замена подстроки в строке — str_replace(искомое, замена, строка). ==== STRLEN ==== Длина строки ''в символах''. ==== SUBSTR=SUBSTRING ==== Стандартная (для всех, кроме жавистов) функция подстроки — substr(строка, начало, длина), или substr(строка, начало). Причём начало и длина могут быть отрицательными, тогда они считаются относительно длины строки. ==== TRIM ==== Удаление пробелов из начала и конца строки. ==== SPLIT ==== Разделение строки по регулярному выражению и лимиту — split(RegExp, аргумент, лимит). Лимит необязателен. (см. [http://perldoc.perl.org/functions/split.html perldoc -f split]) ==== S=HTML=HTMLSPECIALCHARS, T=STRIP, H=STRIP_UNSAFE, NL2BR ==== * Преобразование символов < > & " ' в HTML-сущности.* Удаление всех [[lib:HTML|HTML]]/[[lib:XML|XML]] тегов.* Удаление только «небезопасных» HTML-тегов.* Преобразование переводов строк (\n) в HTML-тег <tt><nowiki><br /></nowiki></tt>. ==== CONCAT, JOIN=IMPLODE ==== * Конкатенация всех своих аргументов — concat(аргументы). Конкатенирует также все элементы всех переданных массивов.* Конкатенация элементов массива через разделитель — join(строка, аргументы). Конкатенирует также все элементы всех переданных массивов. ==== SUBST, SPRINTF, STRFTIME ==== Subst — подстановка на места подстрок вида $ЧИСЛО соответствующих параметров функции или элементов переданного массива — subst(строка, $1, $2, …). Sprintf — он и в Африке [http://perldoc.perl.org/functions/sprintf.html sprintf]. Форматирование даты и/или времени с помощью функции [http://www.manpagez.com/man/3/strftime/ strftime] — strftime(формат, дата [, часть_даты]). Формат strftime’овский (например, «%d %b %Y»). Дата может передаваться как один или два аргумента, если два — они конкатенируются через пробел. Далее дата разбирается способом, похожим на wfTimestamp() в MediaWiki. Принимается следующее:* UNIX время.* Времена типа MySQL DATE, MySQL DATETIME, EXIF, ISO 8601, MediaWiki, и любые другие, подпадающие под следующий формат: 1 группа из 4 или более цифр (год) и 2 (месяц, день) или 5 (месяц, день, часы, минуты, секунды) групп по 2 цифры, разделённые любыми нецифровыми символами и в конце — опционально временная зона — 2 цифры, предварённые пробелом, плюсом или минусом. Короче говоря, <pre>^\D*(\d{4,})\D*(\d{2})\D*(\d{2})\D*(?:(\d{2})\D*(\d{2})\D*(\d{2})\D*([\+\- ]\d{2}\D*)?)?$</pre>* Оракловский формат даты-времени: <nowiki>ДД-Мес-ГГ[ГГ] ЧЧ.ММ.СС</nowiki>.* RFC 822. ==== STRLIMIT=TRUNCATE ==== Ограничение длины строки <tt>str</tt> максимальной длиной <tt>len</tt> — <tt>strlimit(str, len, dots = "...")</tt>. Если строка превышает заданную длину, она обрезается предпочтительно по пробелу или Tab’у, а в конец добавляется <tt>dots</tt> или по умолчанию <tt>"..."</tt>, если аргумент <tt>dots</tt> не передаётся. ==== PLURAL_RU ==== Выбор правильного окончания в русском языке в зависимости от количества: <tt>plural_ru(число, один, несколько, много)</tt>. Например (1 шаблон, 2-3-4-102 шаблонА, 5-6-15-… шаблонОВ): <tt><nowiki>{num} шаблон{plural_ru(num, '', 'а', 'ов')}</nowiki></tt> === Массивы и хеши === ==== HASH ==== Создание хэша из всех аргументов. Соответственно в хеше аргументы идут парами КЛЮЧ => ЗНАЧЕНИЕ, КЛЮЧ => ЗНАЧЕНИЕ и т. п. ==== KEYS, HASH_KEYS, ARRAY_KEYS ==== Массив ключей хэша. Понятное дело, в PHP их порядок сохраняется, а в Perl — нет. ==== SORT ==== Сортировка массива по значениям. ==== PAIRS ==== Массив хэшей вида <tt>{ key => ключ, value => значение }</tt> для хэша, в случае Perl ключи будут отсортированы по имени. ==== ARRAY, RANGE ==== * Создание массива.* Диапазон от A до B — range(A, B). ==== IS_ARRAY ==== Проверка, является ли аргумент массивом. В PHP-версии не проверяется, а не является ли он при этом хэшем, ибо трудоёмко. ==== COUNT, SUBARRAY=ARRAY_SLICE, SUBARRAY_DIVMOD ==== * Количество элементов массива, или 0, если аргумент — не массив — count(аргумент).* Аналог функции [http://php.net/manual/en/function.array-slice.php array_slice] из PHP.* Выбор из массива каждого div’того элемента, начиная с номера mod или нуля по умолчанию — subarray_divmod(массив, div, mod). ==== GET ==== <tt>GET(что)</tt> — получение значения переменной верхнего уровня. ==== SET ==== <tt>SET(куда, что)</tt> — присваивание «куда» значения «что». Уравнения, понятное дело, не решает, то есть, как и обычно, присваивать можно только lvalue :) ==== ARRAY_MERGE ==== Слить массивы в один. Под Perl — только массивы (не хеши), под PHP — любые массивы. ==== SHIFT, POP, UNSHIFT, PUSH ==== * Вынуть элемент из начала массива — <tt>shift(array)</tt>* Вынуть из конца — <tt>pop(array)</tt>* Добавить в начало — <tt>unshift(array, value)</tt>* Добавить в конец — <tt>push(array, value)</tt>. === Включения === Сюда относятся функции выполнения других шаблонов и/или их блоков. Во все эти функции можно передавать «данные» (tpldata) либо с помощью создания хеша функцией hash(), либо просто передачей аргументов как <tt>КЛЮЧ => ЗНАЧЕНИЕ, ...</tt>. ==== PARSE=INCLUDE=PROCESS ==== Включение другого шаблона. <pre>parse('имя файла')parse('имя файла', hash( ключ => значение, ... ))parse('имя файла', ключ => значение, ...)</pre> ==== PARSE_INLINE=INCLUDE_INLINE=PROCESS_INLINE ==== Включение кода не из файла, а просто из строки — <tt>parse_inline('код шаблона'[, аргументы])</tt>. ==== EXEC ==== Включение блока из текущего шаблона — <tt>exec('имя блока'[, аргументы])</tt>. ==== EXEC_FROM ==== Включение блока из другого шаблона — <tt>exec_from('имя файла', 'имя блока'[, аргументы])</tt>. ==== EXEC_FROM_INLINE ==== Ещё больше не рекомендуется, но можно вызывать и функции из кода из строки — <tt>exec_from_inline('код шаблона', 'имя блока'[, аргументы])</tt>. === Прочее === ==== VOID ==== Вычислить аргумент и вернуть пустую строку. Потенциально нужно для игнорирования результата, ибо все возвращаемые значения радостно подставляются в выходной поток. ==== RAW ==== Пустое преобразование первого аргумента со снятием флага «небезопасности». Нужно для подстановки значений «как есть» в HTML-безопасном режиме авто-экранирования. ==== DUMP=VAR_DUMP ==== Вывод всех данных из структуры — Dumper в Perl’е и var_dump в PHP. ==== JSON ==== Форматирование любой структуры данных в формат JSON. ==== CALL ==== Вызов метода объекта по «динамическому» имени — <tt>call(varref, method_name, arg1, arg2, arg3, ...)</tt>. ==== MAP ==== Применение функции, имя которой передано как первый аргумент, ко всем переданным аргументам и элементам всех переданных массивов — map(«имя_функции», аргументы). == Изменения относительно старых версий == * Ликвидированы assign_vars(), assign_block_vars(), tr_assign_vars() — теперь, как и обычно, передаётся просто хеш с данными $vars* Синтаксис типа {a->key} ликвидирован* Авто-переводы, которые были в перловой версии — тоже тю-тю (хотя, может, и будут возрождены)* SET теперь заканчивается обычным END, а не ENDSET* Обращение к счётчику цикла теперь {block_index}, а не {block.#}* Добавлены функции, операторы* Добавлены детальные сообщения об ошибках* Добавлен встроенный фильтр для ликвидации пробелов из начал/концов каждой строки шаблона == Различия PHP и Perl версий == ==== Кэширование работает по-разному ==== В целом, общий смысл — сделать так, чтобы шаблоны было не стыдно вызывать много раз, как много раз за один запрос, так и в целом, при этом максимально использовать механизмы интерпретатора самого языка. Но механизмы для этого применяются разные. Основная причина различий следующая: * Perl: считается, что всё прогрессивное человечество уже давно использует <tt>mod_perl</tt> или [[Платформы для запуска Perl веб-приложений|другие способы запуска веб-приложений]], при которых частых переинициализаций интерпретатора не происходит. Иными словами, ''никто больше не использует CGI''. Таким образом, мы легко можем сохранить живой coderef (ссылку на функцию, или кому как больше нравится — анонимную функцию, замыкание, делегат) в промежутке между двумя запросами. Так и живём — скомпилированный шаблон представляет собой просто хеш с набором анонимных функций, которые сохраняются в ''экземпляре объекта VMXTemplate'' и вызываются при обращении к шаблону или его блокам. Также существует и файловый кэш компилированного кода. '''Важное следствие:''' объект VMXTemplate между запросами нужно оставлять живым. Если его убить — кэш полностью очищается.* PHP: интерпретатор PHP всегда инициализируется заново при обработке каждого HTTP-запроса, а живой coderef в промежутке между двумя инициализациями интерпретатора сохранить, видимо, невозможно. Однако предполагается, что всё прогрессивное человечество давно использует APC/XCache/ZendOpCache/eAccelerator, и поэтому, когда текст шаблонов компилируется в файлы, а файлы подгружаются путём require, на самом деле они загружаются не с диска, а из памяти кэшера, причём — в уже скомпилированном виде. Кроме того, так как скомпилированный шаблон представляет собой класс — в рамках одного запроса он загружается максимум 1 раз, последующие вызовы происходят уже очень быстро. Ну и на всякий пожарный — хотя это, возможно, уже особого выигрыша и не даёт — нескомпилированный текст шаблонов тоже кэшируется в кэше переменных APC/XCache/eAccelerator, если таковой присутствует, и не перезагружается с диска лишний раз. Если <tt>reload = false</tt>, лишними считаются все разы, кроме первого, даже если файл шаблона менялся. В Perl действие <tt>reload</tt> немного отличается — <tt>reload = 0</tt> работает так же, как <tt>reload = false</tt> в PHP, но если <tt>reload > 0</tt>, то тексты шаблонов всё-таки перезагружаются с диска при изменении, но не чаще, чем раз в <tt>reload</tt> секунд. В PHP также есть ещё одна проблема — в процессе выполнения невозможно добавить метод в класс без использования извращений типа [http://pecl.php.net/package/classkit classkit], а хочется, потому что сгенерированные из кода шаблона функции должны быть методами — они дёргают разные функции от $this, подразумевая, что это объект класса VMXTemplate. Поэтому компилированный шаблон PHP-версии — это класс, производный от класса VMXTemplate. Как уже сказано выше, единожды за один HTTP-запрос он загружается в память, а при каждом вызове шаблона создаётся пустой объект этого класса, в него записывается ссылка на <tt>tpldata</tt> и поле <tt>parent</tt>, ссылающееся на родительский объект Template, и вызывается метод класса, соответствующий функции шаблона (<!-- FUNCTION … -->). ==== Несколько различается действие <tt>use_utf8 = true</tt> ==== * Общий смысл — «мои шаблоны и страницы в кодировке UTF-8».* PHP: «использовать mb_str* функции для работы со строками в выражениях».* Perl: «я передаю в шаблон все переменные с флагом UTF-8 = On, их можно смело конкатенировать с UTF-ными частями шаблона». Если кто-то не знает, в Perl строки имеют на себе флаг UTF-8 = да или нет, и при конкатенации строки без флага со строкой с флагом строка без флага будет автоматически переведена в UTF-8 из кодировки, соответствующей текущей локали. Что означает двойное UTF-8-кодирование в случае, если строка на самом деле всё-таки в UTF-8, но просто на ней не установлен флаг.*: Для приведения всех переменных шаблона к UTF-8 можно использовать функцию <tt>utf8on()</tt> из <tt>VMXTemplate::Utils</tt> (рекурсивный <tt>Encode::_utf8_on()</tt>). ==== Различается способ вывода ошибок при <tt>print_error = true</tt> ==== * Общий смысл — при <tt>print_error = true</tt> ошибки и предупреждения должны попасть на экран.* PHP: ошибки группируются и выводятся в отдельном div’е в конце страницы, либо просто print’ами по месту возникновения при вызове из консоли.* Perl: текст ошибок прицепляется к выводу шаблонизатора (возвращается вместе с результатом <tt>parse()</tt>). ==== Различается поведение сравнений ==== * PHP: Тип обычных операторов сравнения определяется во время выполнения. То есть, если во время выполнения одно из сравниваемых значений — число, они сравниваются как числа, иначе — как строки.* Perl: Тип обычных операторов сравнения определяется ''во время компиляции''. То есть, если из контекста понятно, что одно из сравниваемых значений — число (если это константа или результат, например, функции count), сравнение будет численным, иначе — строковым.* Пустые массивы и хеши ложны в PHP и истинны в Perl. То есть простая проверка «IF array» (приведение к булеву типу), если array пуст, в PHP вернёт false, а в Perl — true.