Вах вах, какое приятное и элегантное решение в твоем P.S. Вах вах (качаю головой и подбираю слюну)
ssh у modxcloud, как я узрел, есть только на платных тарифах. По крайней мере меня не пустило, а может уже и забанило за 3-4 неудачные попытки войти. Наплевать. Но то, что я уже успел понять используя доступные данные, вполне может повергнуть в шок: там нет апача! Вот так, олдскул уже не рулит. Короче, 0. Linux 64 бита (сборка достоверно не выяснена)
Описал свои взгляды на это в предыдущем комменте. А по поводу распараллеливания: так ведь nginx вроде как это и делает. Если в системе несколько ядер, то под отдельные запросы он использует разные ядра.
Нет, с индексацией проблем нет. Вот посмотри к примеру этот каталог: portfolio-home-ex-ru.fi1osof.modxcloud.com/catalog/ Он Ajax-овый, при это все ссылки — реальные. Просто на сервере логика хитрая. То есть любую ajax-ссылку можно открыть в соседней вкладке, и она будет вести на целевую страницу. Но я имею ввиду именно те динамические элементы, которые не надо индексировать, то есть форму авторизации, личный кабинет и прочие плюшки, которые меняются для конкретного пользователя. Просто в классическом виде, когда пользователь заходит на страницу, для него могут выдаваться данные, которые актуальны конкретно для него. При этом сама страница может быть общая для всех. Так вот, все, что общее (саму страницу), после первого захода загонять в кеш и уже нгинксом отдавать при последующих заходах. А все индивидуальное — все через AJAX. P.S. еще одно интересное AJAX-решение: omaxgames.fi1osof.modxcloud.com/ (попробуй скопировать адрес внутренней страницы и открыть в отдельной вкладке (вставив адрес, как будто первый заход)).
Давай, даже только для своих проектов это уже стоит того, чтобы оно было.
Кроме того, у меня есть приятель-админ многопосещаемого ресурса, там применяется похожая идеология, но у него сайт не строится динамически под каждого юзера, поэтому скриптами он подгружает данные о логине и всё. А если сайт строится под юзера или строится относительно страны посещения? И так далее, тут динамика и еще раз динамика. Я решил это так: для анонимуса всё уже закешировано до мозга костей, причем всё лежит уже в HTML, подгружая например курсы валют и погоду отдельно. Но это наталкивается на такие вещи, как мультиязычность сайта, что тоже должно быть динамическим. И так далее. Короче говоря, одно из решений — это распаралеливание работы php на все ядра, как я вижу. А не так, как обычно: пока страница грузится, одно ядро занято на 100%, а 7 ядер, к примеру, простаивают. Куда-то сюда я думаю надо копать.
Я думал о похожем решении, но именно Ajax и остановил меня, так как он не индексируется поиском вообще. Чтобы ни говорили, он НЕ индексируется или, по крайней мере, у меня неправильный ajax. Но как ajax может быть неправильным? А хрень в стиле site.ru/page1# ради выдачи поиску не-Ajax версии меня не устраивает в корне. Ajax хорош, если не рассчитывать на поисковые системы вообще и продвигаться другими путями. Например политическими. А вообще в тему, то я так до конца и не понял как еще распараллелить создание выходящей html страницы используя например все ядра процессора, и не использовать при этом Ajax. Частично это таки решается, но не полностью.
В продолжение предыдущего топика. В данном топике я просто выложу некоторые Smarty-шаблоны с сайта, а так же код процессора, который я использовал на замену Wayfinder-у. Под катом много кода и комментов. 1. Smarty-шаблоны. Как я и говорил не раз, phpTemplates+Smarty — это то, что нам позволяет значительно снизить нагрузку на MODX-сайт. Но помимо этого Smarty-шаблоны имеют одну офигенную штуку, которой в MODX-шаблонизации просто нет, а именно — наследование/расширение шаблонов. Давайте рассмотрим это на примере Smarty-шаблонов из Hamster-а. Основной шаблон (используется остальными расширяющими шаблонами.) <!DOCTYPE html>
<html lang="ru">{config name=site_name assign=site_name}
{* HEAD *}
<head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>{field name=longtitle} | {$site_name}</title> <meta name="keywords" content="{field name=keywords}" /><link rel="shortcut icon" href="/assets/images/favicon.ico" type="image/ico" />
<link rel="stylesheet" media="all" href="/assets/hamster/css/style.css" />
<link rel="stylesheet" media="all" href="/assets/hamster/css/prettyPhoto.css" />
<link type="text/css" rel="stylesheet" href="/assets/components/minishop/css/web/jquery.stickr.css">
<!--[if IE]>
<![endif]-->
<base href="{config name=site_url}" />
{* Eof HEAD *}
</head>
<body>
<section id="wrapper">
<section id="main">
<header>
{* Header *}
<a id="logo" title="{$site_name}" href="/"></a>
<nav id="menu">
{*snippet name=Wayfinder params="startId=`0`&level=`1`"*}
{assign var=params value=[
"startId" => 0
,"level" => 1
,"cacheable" => true
,"id" => "mainMenu"
]}
{processor action="web/menu/getcatalogmenu" ns="hamster" params=$params assign=result}
{assign var=items value=$result.object}
{include file="inc/menu/catalog/outer.tpl"}
</nav>
<div id="phone_order">
Заказ по телефону:<br />+7 (495) 221-90-21<br />+7 (495) 221-90-23<br />+7 (925) 092-28-33
</div>
<div id="user_panel">
<a id="cartLink" href="{link id=4}" title="Корзина">Корзина</a>
<span class="uLogin">[[!uLogin? &providers="vkontakte,facebook,odnoklassniki,twitter,mailru,google" &hidden="" &userGroups="Authorized" ]]</span>
</div>
{* Eof Header *}
</header>
<section id="columns">
<aside id="catalog">
{* Catalog.nav *}
<h3><a href="{link id=2}" title="Каталог товаров">Каталог товаров</a>:</h3>
<div id="product_lists">
{*snippet name=Wayfinder params="startId=`1` &level=`1` &rowTpl=`listRowTpl`"*}
{assign var=params value=[
"startId" => 1
,"level" => 1
,"cacheable" => true
,"id" => "secondMenu"
]}
{processor action="web/menu/getcatalogmenu" ns="hamster" params=$params assign=result}
{assign var=items value=$result.object}
{include file="inc/menu/catalog/outer.tpl"}
</div>
<div id="search">
{* Search *}
<form id="search_form" action="{link id=6}">
<input type="text" placeholder="Поиск по артикулу или названию" name="search[text]" /> <input type="submit" value="Искать" />
</form>
{* Eof Search *}
</div>
<div id="catalog_tree">
{*snippet name="Wayfinder@MainCatalogMenu"*}
{assign var=params value=[
"startId" => 2
,"level" => 4
,"sortBy" => "pagetitle"
,"levelClass" => "level"
,"where" => [
"template" => 2
]
,"cacheable" =>true
,"id" => "catalog"
]}
{processor action="web/menu/getcatalogmenu" ns="hamster" params=$params assign=result}
{assign var=items value=$result.object}
{include file="inc/menu/catalog/outer.tpl"}
</div>
{* Eof Catalog.nav *}
</aside>
<article id="content">
{block name=Breadcrumbs}<div id="breadcrumbs">{snippet name=Breadcrumbs params="showHomeCrumb=`0` ¤tAsLink=`0` &showCurrentCrumb=`0`"}</div>{/block}
{block name=content}
{field name=content}
{/block}
</article>
<div class="clear"></div>
</section>
</section>
</section>
<footer>
{* Footer *}
<section id="footers">
<section id="brands">
<div class="smartlist">
{* БрендыХамстерФокс *}
{snippet name=brands_slider}
{* Eof БрендыХамстерФокс *}
</div>
</section>
<aside class="left">
© Хамстер-Фокс.2012<br />
Все права защищены.
</aside>
<aside class="right">
</aside>
<div class="clear"></div>
</section>
{literal}
<!-- Yandex.Metrika counter -->
<noscript><div><img src="//mc.yandex.ru/watch/19623109" style="position:absolute; left:-9999px;" alt="" /></div></noscript>
<!-- /Yandex.Metrika counter -->
{/literal}
{* Eof Footer *}
</footer>
</body> Заметка: Элементы {*… *} — это комментарии, то есть не обрабатываются и никуда не выводятся. В некоторых комментариях имеются вызовы сниппетов, на замену которым использованы процессоры или типа того. И вот здесь мы сразу рассмотрим что же такое «наследование шаблонов» и почему у нас сразу такой большой шаблон, а не разбросанный на отдельные кусочки, чтобы эти кусочки можно было использовать в других шаблонах (как это традиционно используется в MODX-шаблонах). Для этого давайте опять посмотрим на исходный MODX-шаблон: <!DOCTYPE html>
<html lang="ru">
<head>
[[$Head]]
</head>
<body>
<section id="wrapper">
<section id="main">
<header>
[[$Header]]
</header>
<section id="columns">
<aside id="catalog">
[[$Catalog.nav]]
</aside>
<article id="content">
[[*content]]
</article>
<div class="clear"></div>
</section>
</section>
</section>
<footer>
[[$Footer]]
</footer>
</body> В данном случае MODX-шаблон конечно выглядит компактней. Но если нам нужен еще один шаблон, похожий, но с мелкими изменениями, нам придется полностью копировать этот шаблон. А дальше хорошо, если изменения где-то в общем чанке. А если нам надо внести изменения не в общий кусочек кода? И в дальнейшем получится, что если у нас накопится штук 10 шаблонов, и надо внести изменения в шаблоны, то может оказаться, что нам придется вносить изменения во все шаблоны. Плюс постоянный поиск по всем этим чанкам тоже порой отнимает не мало времени (я уже не говорю про потерю в производительности, так как сейчас речь вообще не об этом). А что мы имеем в Smarty? Вот код еще одного шаблона, который не имеет отличий от базового шаблона: {extends file="layout.tpl"} Да, это все! То есть мы просто использовали другой шаблон и все. Никакого копирования никакого кода. А как будет выглядеть еще один шаблон, который имеет отличия от базового шаблона? Вот так, к примеру, выглядит расширяющий шаблон каталога на Hamster-е: {extends file="layout.tpl"}
{block name=content} <div class="products smartlist list">[[!Catalog]]</div> {/block} И да, это тоже все! То есть мне надо было всего лишь заменить блок вывода, чтобы не content текущей страницы выводился, а каталог, плюс он имел бы div-обрамление. Давайте разберем как это работает. 1. Подключаем основной шаблон (обязательно в начале шаблона): {extends file="layout.tpl"} 2. При расширении шаблона весь последующий код расширяющего шаблона просто так не воспринимается. В таких случаях должны использоваться специальные конструкции-блоки. Вот, найдите в основном шаблоне вот такой блок: {block name=content} {field name=content} {/block} {field name=content} — это то же самое, что и [[*content]] Блок обязательно имеет свое имя (в данном случае name=content). Все остальное, что имеется внутри этого блока, выводится как есть. Но если мы расширяем шаблон и используем новый блок с таким же названием, то этот блок замещается содержимым нового блока. В нашем случае это: {block name=content} <div class="products smartlist list">[[!Catalog]]</div> {/block} То есть на выходе в расширяющем шаблоне мы имеем не {field name=content}, а это: <div class="products smartlist list">[[!Catalog]]</div> А в остальном это тот же самый шаблон. При этом шаблоны могут иметь сколько угодно уровней вложенности. То есть этот расширяющий шаблон можно расширить другим шаблоном, и изменить любой из их общих блоков. А если нам к каком-то новом шаблоне надо воткнуть код туда, где вообще не предполагалось изменений, то мы просто вставим в основном шаблоне пустой блок, и в новом шаблоне переопределим его. Вот поэтому я и использую вот такой один общий шаблон, так как в таком случае все перед глазами и работает быстро (без лишних инклюдов и т.п.), и при этом нет вообще потери в гибкости (благодаря Smarty). Еще примеры шаблонов. С заголовком по условию: {extends file="layout.tpl"} {block name=content} <h1>{if $modx->resource->parent == 3}Бренд «{field name=pagetitle}»{else}{field name=pagetitle}{/if}</h1> <div class="products smartlist list">[[!Catalog.Category]]</div> {/block} С некешируемыми MODX-тегами. Quip: {extends file="layout.tpl"}
{block name=content} <h1 id="pagetitle">{field name=pagetitle}</h1>
{field name=content}
<div class="post-comments" id="comments">[[!Quip?
&thread=`blog-post-[[*id]]`
&threaded=`1`
&dateFormat=`%d/%m/%Y %H:%I`
&tplComment=`commentTpl`
&closeAfter=`30`
]]
<br /><br />
[[!QuipReply?
&thread=`blog-post-[[*id]]`
&requireAuth=`1`
&moderate=`0`
&tplAddComment=`commentWithUlogin`
&tplLoginToComment=`authToComment`&closeAfter=`30`
]]
</div>
{/block} В общем, со Smarty-шаблонами творить можно что угодно. 2. Замена Wayfinder. Внимание! Если вы используете Smarty-блоки, внимательно читайте этот комментарий, чтобы избежать бесконечной рекурсии. Вот это, пожалуй, лучшая наработка и того, что было сделано на Hamster-е. Планирую ее в дальнейшем оформить в пакет и постепенно дорабатывать. Для начала разберем, как это дело работает (кстати, при этом мы увидим еще один интересный фокус со Smarty-шаблонами). Сам процессор. «Сердце» модуля — этот процессор. Его главное предназначение — сделать выборку документов, участвующих в формировании менюшки. Вот его код: <?php
class modWebMenuGetCatalogMenuProcessor extends modProcessor{
protected $activeIDs = array(); // ID of active parents
public function initialize(){
$this->setDefaultProperties(array(
'id' => 'menu', // Menu id
'cacheable' => false,
'startId' => $this->modx->resource->id,
'level' => 1,
'sortBy' => 'menuindex',
'sortOrder' => 'ASC',
'levelClass' => '',
'activeClass' => 'active',
'ignoreHidden' => false,
'showUnpublished' => false,
));
return parent::initialize();
}
public function process() {
$output = '';
// get active parents
if(!empty($this->modx->resource) AND $this->modx->resource instanceOf modResource){
$resource = $this->modx->resource;
$this->activeIDs[] = $resource->id;
while($resource = $resource->getOne('Parent')){
$this->activeIDs[] = $resource->id;
}
}
// get menu items
if(!$items = $this->getMenuItems()){
return;
}
// prepare menu items
$items = $this->prepareMenu($items);
return array(
'success' => true,
'message' => '',
'object' => $items,
);
}
public function getMenuItems(){
$items = array();
$startId = $this->getProperty('startId');
$level = $this->getProperty('level');
$cacheable = $this->getProperty('cacheable');
$id = $this->getProperty('id', 'menu');
$cacheKey = $this->modx->context->key."/{$id}/{$startId}";
if($cacheable){
if($fromCache = $this->modx->cacheManager->get($cacheKey)){
return $fromCache;
}
}
//else
if($items = $this->getItems($startId, $level)){
if($cacheable){
$this->modx->cacheManager->set($cacheKey, $items);
}
}
return $items;
}
protected function getItems($parent, $level){
$level--;
$items = array();
$q = $this->modx->newQuery('modResource');
$where = $this->getDefaultConditions();
$where['parent'] = $parent;
$q->where($where);
$q->select(array(
'id', 'parent', 'pagetitle', 'longtitle', 'description', 'menutitle', 'link_attributes', 'uri', 'alias',
));
$q->sortby($this->getProperty('sortBy'), $this->getProperty('sortOrder'));
if($q->prepare() && $q->stmt->execute()){
while($row = $q->stmt->fetch(PDO::FETCH_ASSOC)){
if($level>0){
$row['childs'] = $this->getItems($row['id'], $level);
}
else{
$row['childs'] = array();
}
$items[$row['id']] = $row;
}
}
return $items;
}
protected function prepareMenu(array & $items, $currentlevel=1){
$levelClass = $this->getProperty('levelClass');
$activeClass = $this->getProperty('activeClass');
foreach($items as &$item){
$cls = array();
if($levelClass){
$cls[] = "{$levelClass}{$currentlevel}";
}
$item['linktext'] = ($item['menutitle'] ? $item['menutitle'] : $item['pagetitle']);
if(in_array($item['id'], $this->activeIDs)){
if($activeClass){
$cls[] = $activeClass;
}
}
$item['cls'] = implode(" ", $cls);
if($item['childs']){
$item['childs'] = $this->prepareMenu($item['childs'], $currentlevel+1);
}
}
return $items;
}
protected function getDefaultConditions(){
$where = array(
'deleted' => 0,
);
if(!$this->getProperty('showUnpublished')){
$where['published'] = true;
}
if(!$this->getProperty('ignoreHidden')){
$where['hidemenu'] = false;
}
if($_where = $this->getProperty('where')){
$where = array_merge($where, $_where);
}
return $where;
}
} return 'modWebMenuGetCatalogMenuProcessor'; ?> Помимо выборки документов, этот процессор умеет кешировать результат и в дальнейшем использовать его для формирования меню. Возвращает процессор массив документов (элементов меню). Конечно процессор не все поля документов получает, а только самые необходимые. В дальнейшем я планирую его серьезно доработать/переработать, чтобы он был еще более гибкий (есть ряд мыслей, включая ввод методов типа setSelection и объединение методов getItems и prepareMenu), но это чуть позже. Далее остается только набить эти данные в шаблоны, чтобы сформировать конечный HTML-код шаблона. И вот здесь как раз я и покажу еще одну фишку Smarty-шаблонов, которая мне очень понравилась :-) Итак, рассмотрим пример вызова этого процессора и формирование менюшки. Вот код: {assign var=params value=[ "startId" => 2 ,"level" => 4 ,"sort" => "sortBy" ,"levelClass" => "level" ,"where" => [ "template" => 2 ] ,"cacheable" =>true ,"id" => "catalog" ]}
{processor action="web/menu/getcatalogmenu" ns="hamster" params=$params assign=result}
{assign var=items value=$result.object}
{include file="inc/menu/catalog/outer.tpl"} Что здесь происходит? 1. Набиваем массив параметров, которые мы передадим в процессор: {assign var=params value=[
"startId" => 2 // Стартовый раздел
,"level" => 4 // Количество уровней вложенности
,"sortBy" => "pagetitle" // Сортировка по заголовку
,"levelClass" => "level" // класс уровня (будет level1, level2 и т.п.)
,"where" => [ // Условия поиска
"template" => 2 // документы с шаблоном 2
]
,"cacheable" =>true // Кешировать результат
// ID меню (использется в формировании ключа кеша,
// чтобы случайно не пересекся с кешем других менюшек)
,"id" => "catalog"
]} 2. Вызываем процессор: {processor action="web/menu/getcatalogmenu" ns="hamster" params=$params assign=result} ns — это namespace, то есть пространство имен модуля в MODX.
assign=result — это присваиваем полученный результат переменной $result. 3. Присваиваем массив полученных элементов переменной $items: {assign var=items value=$result.object} 4. Подгружаем Smarty-шаблон, в котором будет выполняться оформление этого массива в конечный код менюшки: {include file="inc/menu/catalog/outer.tpl"} Вот код этого шаблончика: <ul>
{foreach $items as $item}
{* Wrapper *}
{include file="inc/menu/catalog/row.tpl"}
{/foreach}
На самом деле можно смотреть в сторону nginx+APC+MODX. Получится очень хорошо. Только все динамические части сайта надо на AJAX переносить, чтобы nginx мог отдавать полный кеш страницы без обращения к MODX-е в принципе.
устроим свой хостинг? Вот как раз это нам и предстоит с тобой сделать :-) Но не хостинг в чистом виде (то есть я не планирую делать хостинг, на котором основной статьей дохода были бы услуги хостинга), а кое что другое. Когда я доберусь это делать, я тебе обязательно маякну. Буду надеяться, что получится заняться этим очень скоро.