Полностью согласен, так работать с шаблоном намного удобнее. Modxsmarty пока не успел опробовать (работа срочная навалилась), но думаю скоро наверстаю. Но по опыту работы с шаблонизатором Twig (где так же есть плюшки типа вложеных циклов и логических операторов, да еще и наследование шаблонов, которые в принципе способны заменить тот же phx), могу сказать, что потом со стандартным шаблонизатором modx работаешь, так сказать с кривой улыбкой. Вроде бы все хорошо, столько всего понапихано, но скорость не та, от кучи чанков и сниппетов меню раздуто кук бурдюк. В общем было бы замечательно если бы modx из коробки поддерживал бы еще парочку шаблонизаторов на выбор)
Ага, и перетерли массив объектов $this->_relatedObjects[$alias] новым массивом массивов. Вы слышали про ссылки на объекты? Вот такой пример рассмотрим:
<?php $a = $modx->getCollection('modResource', 3); $b = $modx->getCollection('modResource', 3); $obj = $a[3]; print ($a[3] === $obj ? 1: 0); // result 1 $obj = $b[3]; print ($a[3] === $obj ? 1: 0); // result 0
Хотя это вроде одинаковые объекты с одинаковыми данными, он не равны друг другу, так как это разные инстансы (если жестко сравнивать). А вы предлагаете этот массив затирать.
Конечно, может ваш метод и рабочий, может даже сбоев не будет, но он может содержать много подводных камней. Метод перебора foreach и присвоение по id дает четкое понимание что и как происходит, и не заставит программиста лезть в мануалы изучать специфики редко используемых функций.
небольшой фокус :-)
<?php print '<pre>'; $a = array( 2 => 'object' ); $b = array( 2 => 'new object', 5 => 'object', 7 => 'object', ) + $a; print_r($b);
результат:
Array ( [2] => new object [5] => object [7] => object )
Верстаю вот такую форму:
?
Это не просто статика, а результат поиска номеров, то есть информация динамичная, довольно обширная и с элементами хитрой логики (различное количество номеров и вариантов, дополнительные услуги, галерея и т.п.). Я вам по опыту могу сказать, что если это делать на традиционных чанках-сниппетах, то легче было бы убиться об стену, так как объем кода был бы просто огромным, производительность на нуле, делалось бы это недели две минимум и ни один программист бы не взялся такое сопровождать.
А вот это код этого блока на modxSmarty (не законченный еще, но дает полное представление о плюсах применения):
<h1>Доступные номера</h1> <p style="padding-top:10px; font-weight:bold;"> Выберите понравившийся номер и необходимые дополнительные услуги </p> {processor ns=Hotel action="web/reservation/step/search/index" assign=result nocache} {if $result.success==true} <table cellspacing="0" cellpadding="0" border="0" id="room-types-cont" class="base-cont"> <thead><tr> <td class="cell left-holder"> </td> <td class="cell name">Категория номера</td> <td colspan="2" class="cell price"> Цена за <strong>1 ночь</strong>, <span class="currency">руб.</span></td> <td class="cell quantity">Количество номеров</td> <td class="cell amount right-cell">Стоимость, <span class="currency">руб.</span></td></tr> </thead> <tbody> {* All rooms *} {foreach from=$result.object key=room_id item=room nocache} {* counf variables *} {assign var=variables_total value=count($room.variables)} <tr> <td class="cell left-holder" rowspan="{$variables_total+2}"> </td> <td class="cell" rowspan="{$variables_total+2}"> <div class="title">{$room.room.title}</div> </td> <td class="cell empty-cell"> </td> <td class="cell empty-cell"> </td> <td class="cell empty-cell"> </td> <td class="cell empty-cell"> </td> </tr> {* All variables *} {foreach from=$room.variables key=variable_id item=variable} <tr> <td class="cell empty-cell"> </td> <td class="cell empty-cell"> </td> <td class="cell empty-cell"> </td> <td class="cell empty-cell"> </td> </tr> {/foreach} {* Eof All variables *} <tr class="room-type"> <td class="cell persons empty-cell border-top-0"> </td> <td class="cell price empty-cell border-top-0"> </td> <td class="cell quantity empty-cell border-top-0"> </td> <td class="cell amount empty-cell border-top-0"> </td> </tr> {/foreach} {* Eof All rooms *} </tbody> </table> {else} {$result.message} {/if}
Многие очень сильно путают логику (в программном плане), и логику в шаблонах, и утверждают, что в шаблонах логики не должно быть. Вот это большое и опасное заблуждение. Есть логика на сбор, обработку, модификацию и запись информации (толстый клиент с обширными правами), а есть узкопрофильная логика (тонкий клиент). Логика в шаблонизации — чаще всего — это просто логика на чтение. То есть мне (как верстальщику) скормили информацию, сказали (вот тебе такие-то переменные, у тебя там будет то-то и то-то), и я беру и оформляю все это. Но не просто размещаю плейсхолдеры, а могу на основе исходных данных закладывать какую-то логику визуализации (к примеру, подсчитать количество полученных элементов, выполнить валидацию данных и т.п.), и на основе этого следать, к примеру, выпадающие списки, указать различные колспаны и т.п., запрограммировать javascript-плагинчик. Это огромные возможности по визуализации имеющихся сырых данных без необходимости каждый раз дергать программиста «а мне вот еще такая переменная нужна, чтобы это была сумма вот этих трех переменных».
Вот давайте теперь более предметно рассмотрим представленный код.
Здесь единственное, что досталось от программиста — это процессор «web/reservation/step/search/index» из пространства имен «Hotel». Мне программист сказал «вот этот процессор будет тебе возвращать информацию о всех найденных номерах, их количестве, возможности для бронирования, описание и т.п., там будут переменные с такими-то именами и такими-то типами данных».
Я беру и вызываю этот процессор: {processor ns=Hotel action=«web/reservation/step/search/index» assign=result nocache}
Все. Результат выполнения процессора присвоится указанной мною переменной $result.
Далее я могу проверить статус выполнения процессора. Если $result.success == true, то значит процессор выполнился успешно, и как мне сказал программист, все данные по номерам будут доступны мне в массиве $result.object; Вот я делаю проверку:
{if $result.success==true}
А если $result.success не будет равен true, то сообщение об ошибке будет мне доступно в переменной $result.message (это мне тоже подсказал добрый программист, так как сам я верстальщик, и вообще не имею доступа к коду этого процессора, для меня это — черный ящик).
Вот я вывожу это сообщение в блоке «иначе»:
{else} {$result.message} {/if}
А в основном блоке (если у меня все ОК) я в цикле по каждому номеру набиваю конечные HTML-блоки.
{foreach from=$result.object key=room_id item=room nocache}
Внутри каждого цикла, как я и указал, у меня будут массив $room, содержащий всю доступную мне информацию по номеру, и ключик — id-шник номера в переменной $room_id.
Вот я себе комментарий написал, который улучшить читаемость моего кода, и не будет выводиться в брацзер: {* counf variables *}
А вот я подсчитал количество вариантов для конкретного номера {assign var=variables_total value=count($room.variables)}, и это значение присвоил переменной $variables_total. Эта переменная мне понадобится в дальнейшем, чтобы указать правильный роуспан для конкретного блока.
<td class="cell left-holder" rowspan="{$variables_total+2}">
А далее я набиваю уже блоки по каждому конкретному варианту номера.
{foreach from=$room.variables key=variable_id item=variable}
Так же для удобства я могу разбить это на отдельные файлы-шаблоны, и подгружать их через {include file=}, а так же использовать типовые файлы-шаблоны для отдельных шаблонов.
И работает это все гораздо быстрее, чем MODX-парсер. Но в сравнении с MODX-парсером есть еще очень серьезное преимущество — актуальность данных в переменных. Вот простой пример:
[[+var:notempty=`[[!mysnippet]]`]]
Вот здесь MODX сначала выполнит сниппет mysnippet, и только потом, в зависимости от того, выполнилось условие var:notempty или нет, выведет результат сниппета mysnippet или не выведет. Но выполнит он этот сниппет в любом случае.
Вот в этом случае этого не произойдет:
{if $var!=''} {snippet name=mysnippet} {/if}
И вот как раз это и есть реальное отделение логики от визуализации. Верстальщик вообще не лезет в логику процессора. У него есть данные, он их визуализирует. Когда будет менять дизайн, он просто переоформит по-новому имеющиеся сырые данные, но не придется менять логику процессора. Данные будут все те же, просто по-другому сверстаны.
Вы все еще работаете на чистых чанках и сниппетах? Сорри, но крупный проект мы вам не сможем доверить.
UPD: Если вы верстальщик со знанием jQuery, мне нужна небольшая помощь по этой форме. Поиск и все такое я напрограммировал, мне нужна помощь именно по верстке отдельных блоков этого модуля, а так же javascript-программинг этой формы (выбор кол-ва номеров, сопутствующих услуг и подсчет конечной суммы), в общем нужен верстальщик-программист интерфейсов.
UPD2: Помощник нашелся.
<?php print '<pre>'; $a = array( 2=>'object' ); $b = $a + array( 2 => 'new object', 5 =>'object', 7 => 'object', ); print_r($b);
Результат:
Array ( [2] => object [5] => object [7] => object )
То есть элемент с ключом 2 не заменился.
Нет, так нельзя делать. Это ссумируем массивы, а нам надо заменять уже имеющиеся.
Так а разве это не оно:
$a = array("a" => "apple", "b" => "banana"); $b = array("a" => "pear", "b" => "strawberry", "c" => "cherry"); $c = $b + $a; var_dump($c); // array(3) { // ["a"]=> // string(4) "pear" // ["b"]=> // string(10) "strawberry" // ["c"]=> // string(6) "cherry" // }
?
Вот такой вопрос (по хитрому запросу): у меня в базе хранится информация о забронированных номерах. Структура таблицы следующая:
( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `item` int(10) unsigned NOT NULL, `datefrom` datetime NOT NULL, `datetill` datetime NOT NULL, PRIMARY KEY (`id`), KEY `item` (`item`,`datefrom`,`datetill`) )
То есть каждая запись содержит информацию с какого (datefrom) по какое (datetill) номер забронирован. Мне надо по условию (желаемые даты въезда и отбытия) найти свободные номера. То есть это будут номера, для которых на указанные даты нет брони.
Мой запрос такой:
<?php print '<pre>'; $datefrom = "2013-04-06 12:00:01"; $datetill = "2013-04-08 11:59:59"; $c = $modx->newQuery('HotelRoomItem'); $c->leftJoin('HotelRoomReserve', 'Reserves', "Reserves.item=HotelRoomItem.id AND ( (Reserves.datefrom between '{$datefrom}' AND '{$datetill}') OR (Reserves.datetill between '{$datefrom}' AND '{$datetill}') OR (Reserves.datefrom < '{$datefrom}' AND Reserves.datetill > '{$datetill}') )" ); $c->select(array( 'HotelRoomItem.id as item_id', 'Reserves.*', )); $where = array( 'HotelRoomItem.active' => 1, 'Reserves.id' => null, ); $c->where($where);
Конечный запрос:
SELECT HotelRoomItem.id as item_id, Reserves.* FROM `modx_hotel_room_items` AS `HotelRoomItem` LEFT JOIN `modx_hotel_room_reserve` `Reserves` ON Reserves.item=HotelRoomItem.id AND ( (Reserves.datefrom between '2013-04-06 12:00:01' AND '2013-04-08 11:59:59') OR (Reserves.datetill between '2013-04-06 12:00:01' AND '2013-04-08 11:59:59') OR (Reserves.datefrom < '2013-04-06 12:00:01' AND Reserves.datetill > '2013-04-08 11:59:59') ) WHERE ( `HotelRoomItem`.`active` = 1 AND `Reserves`.`id` IS NULL )
Вопрос: есть ли у кого какие-нибудь мысли по этому поводу? Может более оптимальный запрос возможен?
Да нет, калорий много не ушло. Просто столкнулся с новой задачей, и при реализации ее столкнулся с этой багой.
$this->_relatedObjects[$alias]= $collection + $this->_relatedObjects[$alias];
Нет, так нельзя делать. Это ссумируем массивы, а нам надо заменять уже имеющиеся. Для этого правильней использовать foreach и сравнивать по id-ключу.
а если не ошибаюсь, то нужен коммит, чтобы в 2.2.7 зарелизить успели
Да, забыл сказать, тикет я создал: tracker.modx.com/issues/9768 С Джейсоном общался, он взял на заметку. Фикс баги не сложный, так что наверняка скоро пофиксят.
С большой радостью и надеждой на новые перспективы Клуба сообщаю о том, что на нашем сайте зарегистрировался Jason Coward opengeek !
Напомню, что Джейсон — один из команды MODX и главный ее архитектор; разработчик xPDO и некоторых очень популярных пакетов, таких как getResources; и просто очень мощный программист мирового уровня.
Теперь можете быть уверены — для нас нет нерешаемых задач в MODX :)
Боюсь представить — сколько нервных клеток ушло на отлов такого бага. Но по логике, правильней будет так:
$this->_relatedObjects[$alias]= $collection + $this->_relatedObjects[$alias];
отсюда, хотя могу и ошибаться. а если не ошибаюсь, то нужен коммит, чтобы в 2.2.7 зарелизить успели