Шаблонизатор VMX::Template/Из раннего: I.GC — различия между версиями
м (Новая страница: «''Данный документ оставлен в живых по исключительно историческим причинам.'' Это один из пе...») |
|||
Строка 1: | Строка 1: | ||
− | ''Данный документ оставлен в живых по исключительно историческим причинам.'' Это один из первых скриптов, которые я написал на | + | ''Данный документ оставлен в живых по исключительно историческим причинам.'' Это один из первых скриптов, которые я написал на PHP — тот самый «Index.Generate и Index.Compile», о котором говорится [[Шаблонизатор VMX::Template/Старая PHP-версия|тут]]. На этом работала моя первая-первая домашняя страничка на h11.ru. |
+ | |||
+ | Ну что тут сказать? | ||
+ | * Я тогда не знал, что в базе данных бывают индексы и первичные ключи, и использовал её как INI-файл. | ||
+ | * Любовь к зюкам-хрюкам началась давно и сразу. | ||
+ | * Это ж надо было столько кода понаписать… | ||
Написано 2006-04-24. <!-- старая ссылка: http://vmx.yourcmc.ru/filelist.php?file=135 --> | Написано 2006-04-24. <!-- старая ссылка: http://vmx.yourcmc.ru/filelist.php?file=135 --> | ||
Строка 5: | Строка 10: | ||
----- | ----- | ||
− | + | == VMX index.php 1.06 == | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | index. | + | index.php — php-скрипт, который строит HTML код (на самом деле — вообще любой файл, но предназначен именно для генерации HTML-кода), основываясь на шаблоне и данных БД MySQL. |
Зачем оно надо? Веб-дизайнер может создать ПУСТОЙ макет страницы, расставить управляющие коды и получить готовый к использованию шаблон. Далее к сайту создаётся БД (например, через gendbv2.php), и весь сайт работает сгенерированный через index.php. | Зачем оно надо? Веб-дизайнер может создать ПУСТОЙ макет страницы, расставить управляющие коды и получить готовый к использованию шаблон. Далее к сайту создаётся БД (например, через gendbv2.php), и весь сайт работает сгенерированный через index.php. | ||
− | Естественно, шаблонов может быть | + | Естественно, шаблонов может быть несколько — например, один для страницы с новостями, другой для страницы с файлами, третий ещё для чего-нибудь… Несколько шаблонов вместе образуют тему. Темы располагаются в подпапке themes/ относительно папки с index.php. Каждая тема лежит в отдельной подпапке: themes/<имя_темы> |
− | Названия всех таблиц в БД, относящихся к сайту, по умолчанию начинаются с Pages, за это отвечает переменная $PagesTable (см. config.php). Во всех таблицах должна присутствовать индексная колонка | + | Названия всех таблиц в БД, относящихся к сайту, по умолчанию начинаются с Pages, за это отвечает переменная $PagesTable (см. config.php). Во всех таблицах должна присутствовать индексная колонка «Id». Других требований к таблицам НЕТ. При переходе в любую таблицу командой $$jmp к её имени в начало дописывается Pages (вернее, переменная $PagesTable). |
− | Конфигурационные переменные $db, $dbhost, $dbuser, $ | + | Конфигурационные переменные $db, $dbhost, $dbuser, $dbpwd — отвечают за имя БД, хост, на котором расположен сервер MySQL, имя пользователя MySQL и пароль MySQL соответственно. |
− | index.php следует вызывать так: | + | index.php следует вызывать так: «<tt><nowiki>index.php?id=<ИНДЕКС_СТРАНИЦЫ_В_ТАБЛИЦЕ_СТРАНИЦ_БД>&t=<ШАБЛОН>&theme=<ТЕМА></nowiki></tt>». Ни один из этих трёх параметров не является обязательным. По умолчанию id=1, theme=default, t=default, т.е шаблон по умолчанию — это «templates/default/default». |
− | + | Шаблон — любой текстовый файл, в который добавлены управляющие коды. | |
− | В самом начале обрабатываются все команды | + | В самом начале обрабатываются все команды «$$include$FILE» — см.ниже. |
Потом обрабатываются подстановки типа: | Потом обрабатываются подстановки типа: | ||
− | ;<tt><nowiki><<i>></nowiki></tt> ( | + | ;<tt><nowiki><<i>></nowiki></tt> (i — число): подставит значение Id’а записи таблицы, обрабатываемой на уровне вложенности i (0 — весь файл, 1,2,… — циклы) |
;<tt><<#>></tt>: подставит значение текущей обрабатываемой записи в таблице. | ;<tt><<#>></tt>: подставит значение текущей обрабатываемой записи в таблице. | ||
;<tt><<#STR>></tt>: выводится номер записи, обрабатываемой на текущем+длина(STR) уровне вложенности | ;<tt><<#STR>></tt>: выводится номер записи, обрабатываемой на текущем+длина(STR) уровне вложенности | ||
;<tt><<#-STR>></tt>: выводится номер записи, обрабатываемой на текущем-1-длина(STR) уровне вложенности | ;<tt><<#-STR>></tt>: выводится номер записи, обрабатываемой на текущем-1-длина(STR) уровне вложенности | ||
− | ;<tt><<##STR>></tt>: выведется | + | ;<tt><<##STR>></tt>: выведется «#STR» |
;<tt><<%>></tt>: номер текущей итерации | ;<tt><<%>></tt>: номер текущей итерации | ||
;<tt><<@>></tt>: индекс последней вставленной в БД записи (используется функция mysql_insert_id()) | ;<tt><<@>></tt>: индекс последней вставленной в БД записи (используется функция mysql_insert_id()) | ||
− | Имя | + | Имя переменной — далее VAR — это одно из: |
;просто строка: тогда это не переменная | ;просто строка: тогда это не переменная | ||
Строка 43: | Строка 44: | ||
:;<tt><nowiki>*{Inum}</nowiki></tt>: номер текущей итерации цикла, начиная с нуля | :;<tt><nowiki>*{Inum}</nowiki></tt>: номер текущей итерации цикла, начиная с нуля | ||
:;*<tt><nowiki>{amy_query_count}</nowiki></tt>: количество произведённых SQL запросов | :;*<tt><nowiki>{amy_query_count}</nowiki></tt>: количество произведённых SQL запросов | ||
− | ;<tt><nowiki>[FieldName]</nowiki></tt>: тогда значение будет прочитано из поля FieldName текущей обрабатываемой записи. Эквивалентно [<<#>>\FieldName], где # | + | ;<tt><nowiki>[FieldName]</nowiki></tt>: тогда значение будет прочитано из поля FieldName текущей обрабатываемой записи. Эквивалентно [<<#>>\FieldName], где # — номер текущего уровня вложенности. |
;<tt><nowiki>[Id\FieldName]</nowiki></tt>: тогда значение будет прочитано из поля FieldName записи с индексом Id текущей таблицы. | ;<tt><nowiki>[Id\FieldName]</nowiki></tt>: тогда значение будет прочитано из поля FieldName записи с индексом Id текущей таблицы. | ||
− | ;<tt><nowiki>[#Special\FieldName]</nowiki></tt>: специальные значения. Вызывают SQL-запрос вида | + | ;<tt><nowiki>[#Special\FieldName]</nowiki></tt>: специальные значения. Вызывают SQL-запрос вида «<tt><nowiki>SELECT Special(FieldName) FROM `TableName`</nowiki></tt>». |
Примеры: | Примеры: | ||
Строка 52: | Строка 53: | ||
;<tt>[#MAX\Id]</tt>: максимальное значение поля Id в текущей таблице | ;<tt>[#MAX\Id]</tt>: максимальное значение поля Id в текущей таблице | ||
− | Если перед любым из этих выражений поставить \, то она преобразуется через mysql_escape_string. Если @, то как html_pbr (преобразование переводов строк в <tt><nowiki><br /></nowiki></tt>, а символов <tt>&<>"'</tt> | + | Если перед любым из этих выражений поставить \, то она преобразуется через mysql_escape_string. Если @, то как html_pbr (преобразование переводов строк в <tt><nowiki><br /></nowiki></tt>, а символов <tt>&<>"'</tt> — в HTML-entity). Если & — тогда преобразование заключается в удалении всех HTML тэгов кроме разрешённых в config.php. Если поставить \\, то это воспримется просто как символ \. Если @@ — то @. Если && — просто &. |
− | Итак: вне команд $VAR$ | + | Итак: вне команд $VAR$ — подстановка переменной. В командах — смотрите отдельно :) |
Команды (ставятся на отдельную строку): | Команды (ставятся на отдельную строку): | ||
− | ;$$sql$QUERY: выполнить запрос QUERY. | + | ;<tt>$$sql$QUERY</tt>: выполнить запрос QUERY. QUERY — может содержать подстановки переменных. |
− | ;$$qor$QUERY: выполнить запрос QUERY и обработать результат в цикле. | + | ;<tt>$$qor$QUERY</tt>: выполнить запрос QUERY и обработать результат в цикле. |
− | ;$$wor$table$where: частный случай предыдущего: | + | ;<tt>$$wor$table$where</tt>: частный случай предыдущего: |
− | :<tt>SELECT * FROM `table` WHERE where</tt> | + | : <tt>SELECT * FROM `table` WHERE where</tt> |
− | : | + | : Значит — обработать в цикле все записи table, соответствующие WHERE-выражению where. where — это вся часть строки после $$wor$. Примечание: Если не хотите никуда переходить — напишите $$wor${table}$… Т.к глобальная переменная PHP table — это текущая таблица. |
− | :Примеры: | + | : Примеры: |
− | :;$$wor$1: обойти всю таблицу. | + | :;<tt>$$wor$1</tt>: обойти всю таблицу. |
− | :;$$wor$Id>=1 AND Id<=5: обойти записи с индексами от 1 до 5. | + | :;<tt>$$wor$Id>=1 AND Id<=5</tt>: обойти записи с индексами от 1 до 5. |
− | :;$$wor$Id>=1 LIMIT 5: обход 5 записей, начиная с индексов больших 1. (см. 9 подсказку) | + | :;<tt>$$wor$Id>=1 LIMIT 5</tt>: обход 5 записей, начиная с индексов больших 1. (см. 9 подсказку) |
− | ;$$for$table$VAR1$VAR2: Частный случай цикла wor. Обход Id>=VAR1 AND Id<VAR1+VAR2 | + | ;<tt>$$for$table$VAR1$VAR2</tt>: Частный случай цикла wor. Обход Id>=VAR1 AND Id<VAR1+VAR2 |
− | ;$$filter$VAR1$VAR2: Цикл, выбирающий из текущего кэша записи, у которых значение колонки VAR1 равно VAR2 | + | ;<tt>$$filter$VAR1$VAR2</tt>: Цикл, выбирающий из текущего кэша записи, у которых значение колонки VAR1 равно VAR2 |
− | ;$$whileXX$VAR1$VAR2: Цикл повторяющий своё тело до тех пор, пока VAR1 XX VAR2 истинно | + | ;<tt>$$whileXX$VAR1$VAR2</tt>: Цикл повторяющий своё тело до тех пор, пока VAR1 XX VAR2 истинно |
− | ;$$cache$WHERE: Кэширование текущей записи или всех записей, соответствующих WHERE | + | ;<tt>$$cache$WHERE</tt>: Кэширование текущей записи или всех записей, соответствующих WHERE |
− | ;$$cache: Кэширование текущей записи | + | ;<tt>$$cache</tt>: Кэширование текущей записи |
− | ;$$cacheassign$LEVEL: присвоить текущий кэш равным кэшу уровня LEVEL | + | ;<tt>$$cacheassign$LEVEL</tt>: присвоить текущий кэш равным кэшу уровня LEVEL |
− | ;$$cleancache: Очистка кэша | + | ;<tt>$$cleancache</tt>: Очистка кэша |
− | ;$$exit: Останов или выход из итерации цикла | + | ;<tt>$$exit</tt>: Останов или выход из итерации цикла |
− | ;$$break: Выход из цикла | + | ;<tt>$$break</tt>: Выход из цикла |
− | ;$$block: Начало блока (например, if'a) | + | ;<tt>$$block</tt>: Начало блока (например, if'a) |
:Пример: | :Пример: | ||
− | :$$block | + | :<tt>$$block</tt> |
− | :$$if<?$VAR1$VAR2 | + | :<tt>$$if<?$VAR1$VAR2</tt> |
− | :... | + | :<tt>...</tt> |
− | :$$/ | + | :<tt>$$/</tt> |
:- это if (VAR1 < VAR2) { ... } | :- это if (VAR1 < VAR2) { ... } | ||
− | ;$$/: конец блока или тела цикла | + | ;<tt>$$/</tt>: конец блока или тела цикла |
− | ;$$ifXX$VAR1$VAR2: оператор условного выхода - проверить VAR1 XX VAR2. Если ложь, выйти. Для цикла "выйти" значит "перейти к следующей записи таблицы". Внимание! Не используйте это в циклах - будет неоптимально. | + | ;<tt>$$ifXX$VAR1$VAR2</tt>: оператор условного выхода - проверить VAR1 XX VAR2. Если ложь, выйти. Для цикла "выйти" значит "перейти к следующей записи таблицы". Внимание! Не используйте это в циклах - будет неоптимально. |
− | ;$$include$FILE: прямое включение шаблона. В FILE никаких переменных! Кроме того, эта команда ДОЛЖНА стоять на отдельной строке. | + | ;<tt>$$include$FILE</tt>: прямое включение шаблона. В FILE никаких переменных! Кроме того, эта команда ДОЛЖНА стоять на отдельной строке. |
− | ;$$php$VAR: включение и выполнение php-кода из файла VAR. | + | ;<tt>$$php$VAR</tt>: включение и выполнение php-кода из файла VAR. |
− | ;$$chg$VAR: переключение на обработку записи под индексом VAR. ВНИМАНИЕ! О кэшировании новой записи она не заботится. | + | ;<tt>$$chg$VAR</tt>: переключение на обработку записи под индексом VAR. ВНИМАНИЕ! О кэшировании новой записи она не заботится. |
− | ;$$wtime: вывести текущее время выполнения. | + | ;<tt>$$wtime</tt>: вывести текущее время выполнения. |
Вниманию оптимизаторов! Следующие три команды очищают кэш. | Вниманию оптимизаторов! Следующие три команды очищают кэш. | ||
− | ;$$jmp$VAR: перейти в таблицу с именем VAR. | + | ;<tt>$$jmp$VAR</tt>: перейти в таблицу с именем VAR. |
− | ;$$ret: вернуться на предыдущую таблицу (история хранится) | + | ;<tt>$$ret</tt>: вернуться на предыдущую таблицу (история хранится) |
− | ;$$aret: вернуться на главную таблицу и очистить историю переходов | + | ;<tt>$$aret</tt>: вернуться на главную таблицу и очистить историю переходов |
Поддерживается вложенная буферизация вывода в 3 режимах - добавление в буфер справа (A+B = AB), слева (A+B = BA), или без буферизации. По умолчанию буферизация 1 уровня справа. | Поддерживается вложенная буферизация вывода в 3 режимах - добавление в буфер справа (A+B = AB), слева (A+B = BA), или без буферизации. По умолчанию буферизация 1 уровня справа. | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | compile.php - специфичные команды и подстановки: | + | ;<tt>$$bufstartR</tt>: начать новый уровень буферизации справа |
+ | ;<tt>$$bufstartL</tt>: начать новый уровень буферизации слева | ||
+ | ;<tt>$$bufchgR</tt>: изменить метод буферизации на буферизацию справа | ||
+ | ;<tt>$$bufchgL</tt>: изменить метод буферизации на буферизацию слева | ||
+ | ;<tt>$$bufclear</tt>: очистить буфер текущего уровня наффиК | ||
+ | ;<tt>$$bufflush</tt>: сбросить буфер текущего уровня в буфер предыдущего уровня | ||
+ | ;<tt>$$bufstop</tt>: закончить текущий уровень буферизации и сбросить буфер в предыдущий | ||
+ | ;<tt>$$bufsave$name</tt>: сохранить буфер под именем name (обращаться потом как ${{name}}$) | ||
+ | ;<tt>$$save$name$value</tt>: сохранить value как name; для index.php $$define$name$value делает то же самое. | ||
+ | |||
+ | <tt>compile.php</tt> - специфичные команды и подстановки: | ||
− | ;$$define$name$value: сохранить value как name НА ВРЕМЯ КОМПИЛЯЦИИ. То есть, разница между $$save и $$define при компиляции такая: если написать $$save$name$value - в php-код запишется строка типа $saved_buffers [name] = value; а при использовании define значение будет подставляться прямо в то место, где используется. При этом все подстановки внутри него всё равно работают. | + | ;<tt>$$define$name$value</tt>: сохранить value как name НА ВРЕМЯ КОМПИЛЯЦИИ. То есть, разница между $$save и $$define при компиляции такая: если написать $$save$name$value - в php-код запишется строка типа $saved_buffers [name] = value; а при использовании define значение будет подставляться прямо в то место, где используется. При этом все подстановки внутри него всё равно работают. |
− | ;$$phpverbatim$filename.php: прямое включение в выходной php-код отрезка файла filename.php начиная с первого <?php и кончая первым ?> | + | ;<tt>$$phpverbatim$filename.php</tt>: прямое включение в выходной php-код отрезка файла filename.php начиная с первого <?php и кончая первым ?> |
− | ;$$phpfunc$filename.php: создастся функция с телом, равным отрезку файла filename.php начиная с первого <?php и кончая первым ?>, и тут же и вызовется | + | ;<tt>$$phpfunc$filename.php</tt>: создастся функция с телом, равным отрезку файла filename.php начиная с первого <?php и кончая первым ?>, и тут же и вызовется |
− | ;подстановка {(VarName)}: это как раз подстановка | + | ;<tt>подстановка {(VarName)}</tt>: это как раз подстановка define’ов. |
Логические операции (XX в $$if и $$while): | Логические операции (XX в $$if и $$while): | ||
Строка 119: | Строка 121: | ||
;==: численное/лексикографическое сравнение на "равно". | ;==: численное/лексикографическое сравнение на "равно". | ||
;!=: численное/лексикографическое сравнение на "не равно". | ;!=: численное/лексикографическое сравнение на "не равно". | ||
− | ;>=: численное/лексикографическое сравнение на | + | ;>=: численное/лексикографическое сравнение на «больше или равно». |
− | ;>: численное/лексикографическое сравнение на | + | ;>: численное/лексикографическое сравнение на «больше». |
− | ;?: проверка, задана ли глобальная переменная PHP | + | ;?: проверка, задана ли глобальная переменная PHP VAR1 — true если задана и непуста как строка |
− | ;!?: отрицание | + | ;!?: отрицание «?» |
Причём, в именах полей/идентификаторах также можно использовать <tt><nowiki><<i>></nowiki></tt> т.к всегда и сначала подставляются <tt><nowiki><<i>></nowiki></tt>. | Причём, в именах полей/идентификаторах также можно использовать <tt><nowiki><<i>></nowiki></tt> т.к всегда и сначала подставляются <tt><nowiki><<i>></nowiki></tt>. | ||
− | Для оптимизации работы с БД записи кэшируются. Вначале, при запуске скрипта кэшируется не больше одной записи с Id равным $recid. Однако потом, в циклах, кэшируется сразу много записей. А | + | Для оптимизации работы с БД записи кэшируются. Вначале, при запуске скрипта кэшируется не больше одной записи с Id равным $recid. Однако потом, в циклах, кэшируется сразу много записей. А именно — все, подходящие под границы цикла. В принципе, это может создать проблемы с памятью при обработке действительно крупных объёмов данных. Но на практике всё будет нормально. |
− | Далее, compile.php. Как и видно из | + | Далее, compile.php. Как и видно из названия — этот скрипт компилирует шаблоны. То есть, создаёт PHP скрипты, делающие то же самое, что и index.php при интерпретации шаблонов. Вызывать его следует так же, как и index.php: <tt><nowiki>/compile.php[?t=TEMPLATE][&theme=THEME]</nowiki></tt>. При этом выход запишется в файл THEME_TEMPLATE.php в ту же папку, где лежит сам compile.php. Шаблоны он берёт оттуда же, откуда и index.php. При работе compile.php меняет все вхождения <tt><nowiki>/index.php[?t=...][&theme=...]</nowiki></tt> на вызов соответствующего откомпилированного скрипта. |
Кроме того, вы можете указать дополнительные опции компилятора шаблонов: | Кроме того, вы можете указать дополнительные опции компилятора шаблонов: | ||
− | ;&no_cache_when_reading: не обновлять кэш, если в нём нету | + | ;&no_cache_when_reading: не обновлять кэш, если в нём нету переменной — немного оптимизирует скрипт по времени работы. Не обновлять — то есть не записывать код, производящий обновление кэша. |
− | ;&generate_comments: генерировать | + | ;&generate_comments: генерировать комментарии — записывать каждый кусок файла в комментарии перед командами, ему соответствующими. |
− | ;&default_theme=DEF: переименовать тему по умолчанию в DEF, если DEF | + | ;&default_theme=DEF: переименовать тему по умолчанию в DEF, если DEF пусто — тогда имена файлов будут просто TNAME.php. |
;&default_t=DEF: переименовать шаблон по умолчанию в DEF. | ;&default_t=DEF: переименовать шаблон по умолчанию в DEF. | ||
− | :Переименование влияет на имена подставляемых вместо /index.php[?t= | + | : Переименование влияет на имена подставляемых вместо /index.php[?t=…][&theme=…] скриптов и на имя выходного скрипта. |
− | Также для удобства создан скрипт compileui. | + | Также для удобства создан скрипт compileui.php — пользовательский интерфейс компилятора. Заполните поля ввода и нажмите кнопку «компилировать тему» — после этого все шаблоны темы с названием, заданным в поле «тема», кроме шаблонов, лежащих в файлах с расширениями, откомпилируются и будут помещены в ту же папку, где лежит compileui.php. compile.php для работы через compileui.php должен находиться в корневой папке сервера. |
− | * Поле | + | * Поле «тема» задаёт компилируемую тему. |
− | * Поле | + | * Поле «тема default=" - аналог параметра компилятора &default_theme=DEF. |
* Поле "шаблон default=" - аналог параметра компилятора &default_t=DEF. | * Поле "шаблон default=" - аналог параметра компилятора &default_t=DEF. | ||
− | * Флажок "Управляю кэшем вручную и | + | * Флажок "Управляю кэшем вручную и корректно» — аналог опции компилятора &no_cache_when_reading. |
− | * Флажок | + | * Флажок «Генерировать комментарии к коду» — аналог опции компилятора &generate_comments. |
− | + | == Подсказки разработчикам == | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | # Во-первых, в принципе, скрипт не ориентирован на ввод информации в БД, хотя он вполне может | + | # Во-первых, в принципе, скрипт не ориентирован на ввод информации в БД, хотя он вполне может осуществляться — через команду $$sql, формы и глобальные переменные PHP. Все переменные запроса импортируются с req_ перед названием. То есть, если у вас на странице есть что-то вроде <tt><nowiki><input type="text" name="helloworld" maxlength=256></nowiki></tt> — то после отправки формы текст из этого поля ввода появится как переменная ${req_helloworld}$. Соответственно, вы можете использовать её в SQL запросе. |
− | # В ту же тему: если ваш движок должен поддерживать некую конфигурацию через config. | + | # В ту же тему: если ваш движок должен поддерживать некую конфигурацию через config.php — допишите в него между «<?php" и "?>» строки вида «$YOUR_VAR_NAME = YOUR_VAR_VALUE;» и далее сможете использовать их как ${YOUR_VAR_NAME}$. |
− | # Чтобы включить форму | + | # Чтобы включить форму аутентификации — нужно написать ОТДЕЛЬНО два php-скрипта — один для вывода формы или приветствия, а другой для обработки команд аутетификации. Связано это с тем, что протокол HTTP не позволяет передавать HTTP Cookies иначе, чем как ДО НАЧАЛА любого вывода скрипта. То есть — скрипт-обработчик команд «вход» и «выход» должен быть включён в САМОМ начале шаблона — на первой строке. Так же и с любыми скриптами, работающими с HTTP Cookies. Скрипт, выводящий форму или приветствие, вы можете размещать где угодно. '''UPD: ЛИБО (проще) в начале включаете буферизацию справа ($$buf+r), и выводите страницу.''' |
− | # Старайтесь оптимизировать работу с базой данных: именно MySQL является узким местом в производительности. В принципе, для этого много умения не надо :) но, например: допустим, есть таблица с категориями файлов, которые образуют иерархическую структуру. Категорий файлов, как правило, немного, и их можно кэшировать прямо все, потому что иерархический обход с использованием | + | # Старайтесь оптимизировать работу с базой данных: именно MySQL является узким местом в производительности. В принципе, для этого много умения не надо :) но, например: допустим, есть таблица с категориями файлов, которые образуют иерархическую структуру. Категорий файлов, как правило, немного, и их можно кэшировать прямо все, потому что иерархический обход с использованием while — штука, которая без ручного кэширования кушать запросы будет только так. Соответственно, перед while переходим в требуемую таблицу, кэшируем все её записи — $$cache$1, и дальше начинаем while. |
− | # Цикл идентифицирует итерации по колонке Id. Так что если её в строках результата не будет, она, хы, доопределится :) | + | # Цикл идентифицирует итерации по колонке Id. Так что если её в строках результата не будет, она, хы, доопределится :) причём — отрицательными значениями. Чтобы было ясно, что это 'несуществующие' индексы. |
− | # Т.к $$sql требуется обычно для SQL-выбора из нескольких таблиц, то имейте ввиду, что в теле цикла, мягко говоря, не рекомендуется использовать записи с индексами кроме выбранных самим | + | # Т.к $$sql требуется обычно для SQL-выбора из нескольких таблиц, то имейте ввиду, что в теле цикла, мягко говоря, не рекомендуется использовать записи с индексами кроме выбранных самим запросом — если выбор шёл из одной таблицы они, при отсутствии в кэше, подгрузятся, а здесь «угадать» таблицу скрипт не сможет. |
− | # Не используйте в циклах операторы условного | + | # Не используйте в циклах операторы условного выхода — неоптимально. |
− | # При включении PHP-скрипта командой $$php скрипт будет | + | # При включении PHP-скрипта командой $$php скрипт будет «по умолчанию» видеть следующие 3 глобальные переменные: |
− | #* $ | + | #* $link — MySQL соединение с БД; |
− | #* $ | + | #* $result — MySQL результат запроса; |
− | #* $ | + | #* $table — имя текущей таблица; |
#: Осторожно обращайтесь с этими переменными во включаемых php-скриптах. При их повреждении страница, скорее всего, отобразится некорректно. | #: Осторожно обращайтесь с этими переменными во включаемых php-скриптах. При их повреждении страница, скорее всего, отобразится некорректно. | ||
− | # | + | # «$$wor$Id>=1 AND Id<=5" и "$$wor$Id>=1 LIMIT 5» — это разные вещи. Потому что если, например, индексов >=1 и <=5 нету, а начинаются они, например, лишь с 7, то первая команда не обойдёт ничего, а вторая обойдёт <= 5 записей, начиная с 7-ой. |
# Вообще говоря - $$include$FILE - это не команда. Собственно интерпретатор даже не видит такие команды - они обрабатываются до него и прозрачно. | # Вообще говоря - $$include$FILE - это не команда. Собственно интерпретатор даже не видит такие команды - они обрабатываются до него и прозрачно. | ||
− | + | == Примеры == | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
Обход таблицы вверх по иерархии. В таблице нам важны поля "name" - имя и "parent" - родительская запись. | Обход таблицы вверх по иерархии. В таблице нам важны поля "name" - имя и "parent" - родительская запись. | ||
− | Выводит типа "КОРЕНЬ >> СЫН >> СЫН >> | + | Выводит типа "КОРЕНЬ >> СЫН >> СЫН >> … >> СЫН >> ТЕКУЩИЙ ЭЛЕМЕНТ" |
<pre> | <pre> |
Версия 02:10, 13 января 2011
Данный документ оставлен в живых по исключительно историческим причинам. Это один из первых скриптов, которые я написал на PHP — тот самый «Index.Generate и Index.Compile», о котором говорится тут. На этом работала моя первая-первая домашняя страничка на h11.ru.
Ну что тут сказать?
- Я тогда не знал, что в базе данных бывают индексы и первичные ключи, и использовал её как INI-файл.
- Любовь к зюкам-хрюкам началась давно и сразу.
- Это ж надо было столько кода понаписать…
Написано 2006-04-24.
VMX index.php 1.06
index.php — php-скрипт, который строит HTML код (на самом деле — вообще любой файл, но предназначен именно для генерации HTML-кода), основываясь на шаблоне и данных БД MySQL.
Зачем оно надо? Веб-дизайнер может создать ПУСТОЙ макет страницы, расставить управляющие коды и получить готовый к использованию шаблон. Далее к сайту создаётся БД (например, через gendbv2.php), и весь сайт работает сгенерированный через index.php.
Естественно, шаблонов может быть несколько — например, один для страницы с новостями, другой для страницы с файлами, третий ещё для чего-нибудь… Несколько шаблонов вместе образуют тему. Темы располагаются в подпапке themes/ относительно папки с index.php. Каждая тема лежит в отдельной подпапке: themes/<имя_темы>
Названия всех таблиц в БД, относящихся к сайту, по умолчанию начинаются с Pages, за это отвечает переменная $PagesTable (см. config.php). Во всех таблицах должна присутствовать индексная колонка «Id». Других требований к таблицам НЕТ. При переходе в любую таблицу командой $$jmp к её имени в начало дописывается Pages (вернее, переменная $PagesTable).
Конфигурационные переменные $db, $dbhost, $dbuser, $dbpwd — отвечают за имя БД, хост, на котором расположен сервер MySQL, имя пользователя MySQL и пароль MySQL соответственно.
index.php следует вызывать так: «index.php?id=<ИНДЕКС_СТРАНИЦЫ_В_ТАБЛИЦЕ_СТРАНИЦ_БД>&t=<ШАБЛОН>&theme=<ТЕМА>». Ни один из этих трёх параметров не является обязательным. По умолчанию id=1, theme=default, t=default, т.е шаблон по умолчанию — это «templates/default/default».
Шаблон — любой текстовый файл, в который добавлены управляющие коды.
В самом начале обрабатываются все команды «$$include$FILE» — см.ниже.
Потом обрабатываются подстановки типа:
- <<i>> (i — число)
- подставит значение Id’а записи таблицы, обрабатываемой на уровне вложенности i (0 — весь файл, 1,2,… — циклы)
- <<#>>
- подставит значение текущей обрабатываемой записи в таблице.
- <<#STR>>
- выводится номер записи, обрабатываемой на текущем+длина(STR) уровне вложенности
- <<#-STR>>
- выводится номер записи, обрабатываемой на текущем-1-длина(STR) уровне вложенности
- <<##STR>>
- выведется «#STR»
- <<%>>
- номер текущей итерации
- <<@>>
- индекс последней вставленной в БД записи (используется функция mysql_insert_id())
Имя переменной — далее VAR — это одно из:
- просто строка
- тогда это не переменная
- {{VarName}}
- сохранённая переменная VarName. Также для index.php {(VarName)} означает то же самое.
- {VarName}
- тогда это будет глобальная переменная PHP VarName; предопределённые:
- *{Inum}
- номер текущей итерации цикла, начиная с нуля
- {amy_query_count}
- количество произведённых SQL запросов
- [FieldName]
- тогда значение будет прочитано из поля FieldName текущей обрабатываемой записи. Эквивалентно [<<#>>\FieldName], где # — номер текущего уровня вложенности.
- [Id\FieldName]
- тогда значение будет прочитано из поля FieldName записи с индексом Id текущей таблицы.
- [#Special\FieldName]
- специальные значения. Вызывают SQL-запрос вида «SELECT Special(FieldName) FROM `TableName`».
Примеры:
- [#COUNT\*]
- количество записей в текущей таблице
- [#MAX\Id]
- максимальное значение поля Id в текущей таблице
Если перед любым из этих выражений поставить \, то она преобразуется через mysql_escape_string. Если @, то как html_pbr (преобразование переводов строк в <br />, а символов &<>"' — в HTML-entity). Если & — тогда преобразование заключается в удалении всех HTML тэгов кроме разрешённых в config.php. Если поставить \\, то это воспримется просто как символ \. Если @@ — то @. Если && — просто &.
Итак: вне команд $VAR$ — подстановка переменной. В командах — смотрите отдельно :)
Команды (ставятся на отдельную строку):
- $$sql$QUERY
- выполнить запрос QUERY. QUERY — может содержать подстановки переменных.
- $$qor$QUERY
- выполнить запрос QUERY и обработать результат в цикле.
- $$wor$table$where
- частный случай предыдущего:
- SELECT * FROM `table` WHERE where
- Значит — обработать в цикле все записи table, соответствующие WHERE-выражению where. where — это вся часть строки после $$wor$. Примечание: Если не хотите никуда переходить — напишите $$wor${table}$… Т.к глобальная переменная PHP table — это текущая таблица.
- Примеры:
- $$wor$1
- обойти всю таблицу.
- $$wor$Id>=1 AND Id<=5
- обойти записи с индексами от 1 до 5.
- $$wor$Id>=1 LIMIT 5
- обход 5 записей, начиная с индексов больших 1. (см. 9 подсказку)
- $$for$table$VAR1$VAR2
- Частный случай цикла wor. Обход Id>=VAR1 AND Id<VAR1+VAR2
- $$filter$VAR1$VAR2
- Цикл, выбирающий из текущего кэша записи, у которых значение колонки VAR1 равно VAR2
- $$whileXX$VAR1$VAR2
- Цикл повторяющий своё тело до тех пор, пока VAR1 XX VAR2 истинно
- $$cache$WHERE
- Кэширование текущей записи или всех записей, соответствующих WHERE
- $$cache
- Кэширование текущей записи
- $$cacheassign$LEVEL
- присвоить текущий кэш равным кэшу уровня LEVEL
- $$cleancache
- Очистка кэша
- $$exit
- Останов или выход из итерации цикла
- $$break
- Выход из цикла
- $$block
- Начало блока (например, if'a)
- Пример:
- $$block
- $$if<?$VAR1$VAR2
- ...
- $$/
- - это if (VAR1 < VAR2) { ... }
- $$/
- конец блока или тела цикла
- $$ifXX$VAR1$VAR2
- оператор условного выхода - проверить VAR1 XX VAR2. Если ложь, выйти. Для цикла "выйти" значит "перейти к следующей записи таблицы". Внимание! Не используйте это в циклах - будет неоптимально.
- $$include$FILE
- прямое включение шаблона. В FILE никаких переменных! Кроме того, эта команда ДОЛЖНА стоять на отдельной строке.
- $$php$VAR
- включение и выполнение php-кода из файла VAR.
- $$chg$VAR
- переключение на обработку записи под индексом VAR. ВНИМАНИЕ! О кэшировании новой записи она не заботится.
- $$wtime
- вывести текущее время выполнения.
Вниманию оптимизаторов! Следующие три команды очищают кэш.
- $$jmp$VAR
- перейти в таблицу с именем VAR.
- $$ret
- вернуться на предыдущую таблицу (история хранится)
- $$aret
- вернуться на главную таблицу и очистить историю переходов
Поддерживается вложенная буферизация вывода в 3 режимах - добавление в буфер справа (A+B = AB), слева (A+B = BA), или без буферизации. По умолчанию буферизация 1 уровня справа.
- $$bufstartR
- начать новый уровень буферизации справа
- $$bufstartL
- начать новый уровень буферизации слева
- $$bufchgR
- изменить метод буферизации на буферизацию справа
- $$bufchgL
- изменить метод буферизации на буферизацию слева
- $$bufclear
- очистить буфер текущего уровня наффиК
- $$bufflush
- сбросить буфер текущего уровня в буфер предыдущего уровня
- $$bufstop
- закончить текущий уровень буферизации и сбросить буфер в предыдущий
- $$bufsave$name
- сохранить буфер под именем name (обращаться потом как $Шаблон:Name$)
- $$save$name$value
- сохранить value как name; для index.php $$define$name$value делает то же самое.
compile.php - специфичные команды и подстановки:
- $$define$name$value
- сохранить value как name НА ВРЕМЯ КОМПИЛЯЦИИ. То есть, разница между $$save и $$define при компиляции такая: если написать $$save$name$value - в php-код запишется строка типа $saved_buffers [name] = value; а при использовании define значение будет подставляться прямо в то место, где используется. При этом все подстановки внутри него всё равно работают.
- $$phpverbatim$filename.php
- прямое включение в выходной php-код отрезка файла filename.php начиная с первого <?php и кончая первым ?>
- $$phpfunc$filename.php
- создастся функция с телом, равным отрезку файла filename.php начиная с первого <?php и кончая первым ?>, и тут же и вызовется
- подстановка {(VarName)}
- это как раз подстановка define’ов.
Логические операции (XX в $$if и $$while):
- <
- численное/лексикографическое сравнение на "меньше".
- <=
- численное/лексикографическое сравнение на "меньше или равно".
- ==
- численное/лексикографическое сравнение на "равно".
- !=
- численное/лексикографическое сравнение на "не равно".
- >=
- численное/лексикографическое сравнение на «больше или равно».
- >
- численное/лексикографическое сравнение на «больше».
- ?
- проверка, задана ли глобальная переменная PHP VAR1 — true если задана и непуста как строка
- !?
- отрицание «?»
Причём, в именах полей/идентификаторах также можно использовать <<i>> т.к всегда и сначала подставляются <<i>>.
Для оптимизации работы с БД записи кэшируются. Вначале, при запуске скрипта кэшируется не больше одной записи с Id равным $recid. Однако потом, в циклах, кэшируется сразу много записей. А именно — все, подходящие под границы цикла. В принципе, это может создать проблемы с памятью при обработке действительно крупных объёмов данных. Но на практике всё будет нормально.
Далее, compile.php. Как и видно из названия — этот скрипт компилирует шаблоны. То есть, создаёт PHP скрипты, делающие то же самое, что и index.php при интерпретации шаблонов. Вызывать его следует так же, как и index.php: /compile.php[?t=TEMPLATE][&theme=THEME]. При этом выход запишется в файл THEME_TEMPLATE.php в ту же папку, где лежит сам compile.php. Шаблоны он берёт оттуда же, откуда и index.php. При работе compile.php меняет все вхождения /index.php[?t=...][&theme=...] на вызов соответствующего откомпилированного скрипта.
Кроме того, вы можете указать дополнительные опции компилятора шаблонов:
- &no_cache_when_reading
- не обновлять кэш, если в нём нету переменной — немного оптимизирует скрипт по времени работы. Не обновлять — то есть не записывать код, производящий обновление кэша.
- &generate_comments
- генерировать комментарии — записывать каждый кусок файла в комментарии перед командами, ему соответствующими.
- &default_theme=DEF
- переименовать тему по умолчанию в DEF, если DEF пусто — тогда имена файлов будут просто TNAME.php.
- &default_t=DEF
- переименовать шаблон по умолчанию в DEF.
- Переименование влияет на имена подставляемых вместо /index.php[?t=…][&theme=…] скриптов и на имя выходного скрипта.
Также для удобства создан скрипт compileui.php — пользовательский интерфейс компилятора. Заполните поля ввода и нажмите кнопку «компилировать тему» — после этого все шаблоны темы с названием, заданным в поле «тема», кроме шаблонов, лежащих в файлах с расширениями, откомпилируются и будут помещены в ту же папку, где лежит compileui.php. compile.php для работы через compileui.php должен находиться в корневой папке сервера.
- Поле «тема» задаёт компилируемую тему.
- Поле «тема default=" - аналог параметра компилятора &default_theme=DEF.
- Поле "шаблон default=" - аналог параметра компилятора &default_t=DEF.
- Флажок "Управляю кэшем вручную и корректно» — аналог опции компилятора &no_cache_when_reading.
- Флажок «Генерировать комментарии к коду» — аналог опции компилятора &generate_comments.
Подсказки разработчикам
- Во-первых, в принципе, скрипт не ориентирован на ввод информации в БД, хотя он вполне может осуществляться — через команду $$sql, формы и глобальные переменные PHP. Все переменные запроса импортируются с req_ перед названием. То есть, если у вас на странице есть что-то вроде <input type="text" name="helloworld" maxlength=256> — то после отправки формы текст из этого поля ввода появится как переменная ${req_helloworld}$. Соответственно, вы можете использовать её в SQL запросе.
- В ту же тему: если ваш движок должен поддерживать некую конфигурацию через config.php — допишите в него между «<?php" и "?>» строки вида «$YOUR_VAR_NAME = YOUR_VAR_VALUE;» и далее сможете использовать их как ${YOUR_VAR_NAME}$.
- Чтобы включить форму аутентификации — нужно написать ОТДЕЛЬНО два php-скрипта — один для вывода формы или приветствия, а другой для обработки команд аутетификации. Связано это с тем, что протокол HTTP не позволяет передавать HTTP Cookies иначе, чем как ДО НАЧАЛА любого вывода скрипта. То есть — скрипт-обработчик команд «вход» и «выход» должен быть включён в САМОМ начале шаблона — на первой строке. Так же и с любыми скриптами, работающими с HTTP Cookies. Скрипт, выводящий форму или приветствие, вы можете размещать где угодно. UPD: ЛИБО (проще) в начале включаете буферизацию справа ($$buf+r), и выводите страницу.
- Старайтесь оптимизировать работу с базой данных: именно MySQL является узким местом в производительности. В принципе, для этого много умения не надо :) но, например: допустим, есть таблица с категориями файлов, которые образуют иерархическую структуру. Категорий файлов, как правило, немного, и их можно кэшировать прямо все, потому что иерархический обход с использованием while — штука, которая без ручного кэширования кушать запросы будет только так. Соответственно, перед while переходим в требуемую таблицу, кэшируем все её записи — $$cache$1, и дальше начинаем while.
- Цикл идентифицирует итерации по колонке Id. Так что если её в строках результата не будет, она, хы, доопределится :) причём — отрицательными значениями. Чтобы было ясно, что это 'несуществующие' индексы.
- Т.к $$sql требуется обычно для SQL-выбора из нескольких таблиц, то имейте ввиду, что в теле цикла, мягко говоря, не рекомендуется использовать записи с индексами кроме выбранных самим запросом — если выбор шёл из одной таблицы они, при отсутствии в кэше, подгрузятся, а здесь «угадать» таблицу скрипт не сможет.
- Не используйте в циклах операторы условного выхода — неоптимально.
- При включении PHP-скрипта командой $$php скрипт будет «по умолчанию» видеть следующие 3 глобальные переменные:
- $link — MySQL соединение с БД;
- $result — MySQL результат запроса;
- $table — имя текущей таблица;
- Осторожно обращайтесь с этими переменными во включаемых php-скриптах. При их повреждении страница, скорее всего, отобразится некорректно.
- «$$wor$Id>=1 AND Id<=5" и "$$wor$Id>=1 LIMIT 5» — это разные вещи. Потому что если, например, индексов >=1 и <=5 нету, а начинаются они, например, лишь с 7, то первая команда не обойдёт ничего, а вторая обойдёт <= 5 записей, начиная с 7-ой.
- Вообще говоря - $$include$FILE - это не команда. Собственно интерпретатор даже не видит такие команды - они обрабатываются до него и прозрачно.
Примеры
Обход таблицы вверх по иерархии. В таблице нам важны поля "name" - имя и "parent" - родительская запись.
Выводит типа "КОРЕНЬ >> СЫН >> СЫН >> … >> СЫН >> ТЕКУЩИЙ ЭЛЕМЕНТ"
$$block $$jmp$HierTable $$cache$1 $$bufstartL $[name]$ $$chg$[parent] $$while!=$<<#>>$0 $[name]$ >> $$chg$[parent] $$/ $$bufstop $$ret $$/
Фактически это просто проход по связному списку, только с той поправкой, что оно добавляет в буфер слева, а потом его выводит и отключает.
Обход таблицы вниз по иерархии. Если было дерево вида:
0 / \ 1 2 / \ 3 4
ТО выведется 0 1 3 4 2.
$$block $$jmp$HierTable $$cache$1 order by `Id` $$save$lastparent$0 $$save$lastwalked$0 $$while>$<<#>>$0 $$save$walking${{lastparent}} $$filter$Parent${{lastparent}} $$if>$<<#>>${{lastwalked}} $$save$walking$<<#>> $$break $$/ $$chg${{walking}} $$save$lastwalked${{walking}} $$save$mif$0 $$block $$if!=${{walking}}${{lastparent}} $$save$mif$1 $$/ $$block $$if>${{mif}}$0 $[Id]$ $$save$lastparent${{walking}} $$save$lastwalked$0 $$/ $$block $$if==${{mif}}$0 $$if>${{lastparent}}$0 $$save$lastparent$[Parent] $$/ $$/ $$ret $$/