Оптимизация phpThumbOf

Наверняка много кто использовал на своих сайтах пакет phpThumbOf — отличная штука, чтобы сделать превьюшки. Простой пример в использовании: [[phpthumbof?input=[[+source]]&options=w=350&h=350&zc=1]] На лету создаст превьюшку 350 на 350. Но с этим пакетом не все так просто.

Проблема первая. Чтение кеша Важно понимать, что это не самостоятельный пакет. Он использует имеющийся в MODX класс phpthumb. А тот в свою очередь уже был ранее замечен, как источник проблем с производительностью. Дело в том, что phpthumb перед созданием кеша файла, подсчитывает общий объем кеша, и это процедура совсем не слабая, если файлов довольно много. Чтобы этого избежать, идем в настройки MODX-а и устанавливаем значение 0 для следующих настроек: phpthumb_cache_maxage, phpthumb_cache_maxfiles и phpthumb_cache_maxsize. Тогда phpthumb не будет подсчитывать объем использованного кеша.

Проблема вторая. Низкая производительность. Сейчас делаю каталог недвижимости, и там на одну страницу выводится 30 карточек. Так вот, на генерацию 30-ти превьюшек уходит 3-4 секунды. Конечно в дальнейшем эти превьюшки уже будут, и повторной генерации не будет, но от этого все равно не легче, так как на сайте более 4000 активных объектов, и будет очень много тормознутых страниц. Да и сниппету каждый раз проверять наличие файла превьюшки — тоже лишняя работа. В общем, эту проблему я решил исправить радикально — просто заранее сгенерировать превьюшки и сохранить их для документа в отдельное TV-поле. Итак, все по порядку:

  1. Создаем TV-шку для превьюхи. С этим все просто и понятно. Единственное — если на сайте будут работать менеджеры, то настройте права доступа на нее, чтобы они даже не видели это поле (это прям в редакторе TV-шки есть вкладка Права доступа).
  2. Пишем плагин на сохранение документа. Замысел прост — при сохранении документа, плагин должен проверять значение TV-шки с необрезанной фоткой, и если получает, то выполняет сниппет phpThumbOf и записывает полученный путь до превьюшки в эту специальную TV-шку. Код плагина:
<?php
switch ($modx->event->name) {
    case 'OnDocFormSave':
        // На всякий случай меняем контекст, если MODX сам не поменял на контекст документа
        $origin_context = null;
        if ($modx->context->key == 'mgr') {
            $origin_context = $modx->context->key;
            $modx->switchContext('web');
        }

        // Создаем самбнейл превьюшки
        $TV_source = 2; // Устанавливаете значения своих TV-шек
        $TV_target = 17;
        $resource = &$scriptProperties['resource'];
        if ($photo = $resource->getTVValue($TV_source) and $thumb = $modx->runSnippet('phpthumbof', array(
            'input' => $photo,
            'options' => 'w=113&h=113&q=100&zc=1',
        ))
            and $photo != $thumb
        ) {
            $resource->setTVValue($TV_target, $thumb);
        }

        // Если контекст меняли, то меняем его на изначальный
        if ($origin_context) {
            $modx->switchContext($origin_context);
        }
        break;
    default:;
}

Все. Теперь при сохранении документа сразу будет генериться и сохраняться превьюшка. Но что делать, если уже документы есть, и их много? Ведь надо каждый документ пересохранить… Но с этим тоже все довольно просто — не обязательно каждый документ открывать, можно документ пересохранить программно, через API. Лучше всего это делать через Console. Пишем и выполняем вот такой код:

<?php
ini_set('max_execution_time', 0);
ignore_user_abort(1);

$TV_source = 1; // Устанавливаете значения своих TV-шек
$TV_target = 2;

$q = $modx->newQuery('modResource');
$q->leftJoin('modTemplateVarResource', 'tv', "tv.tmplvarid={$TV_source} AND tv.contentid=modResource.id");
$q->leftJoin('modTemplateVarResource', 'tv2', "tv2.tmplvarid={$TV_target} AND tv2.contentid=modResource.id");
$q->where(array(
    'tv.id:!=' => null,
    'tv.value:!=' => '',
    'tv2.id' => null,
    'published' => 1,
));
print "Total: " . $modx->getCount('modResource', $q); // Общее число оставшихся документов
// return;

$q->limit(10);

if ($docs = $modx->getCollection('modResource', $q)) {
    foreach ($docs as $doc) {
        $modx->error->reset();
        $modx->runProcessor('resource/update', $doc->toArray());
    }
}

Здесь мы получаем документы с картинками, у которых есть картинки, но нет еще превьюшек, и обновляем их через процессор. А в процессоре уже вызывается событие OnDocFormSave и само собой и наш процессор, срабатывающий на это событие. Вот теперь, после того, как у нас уже есть все превьюшки в специальном TV, нам не надо уже в коде страницы вызывать сниппет phpThumbOf, мы просто вставляем значение этой TV-шки. Нагрузка падает, мы радуемся :-)

UPD: Чуть-чуть дописал плагин. Добавил смену контекста, если текущий контекст — mgr. Дело в том, что для разных контекстов могут быть указаны различные источники файлов, а для mgr этот параметр в TV почему-то не задается, и если источник файлов TV-параметра не базовый (корневой), а контекст mgr, то TV-шка просто вернет неверный результат.

UPD: Добавил еще проверку AND $photo != $thumb. Причина — pThumb, если не смог получить картинку или еще что (ошибка в общем) возвращает оригинальное изображение. Если мы запишем оригинальный путь, то у нас и TV-поле будет заполнено (и мы не будем знать, что для этого ресурса не была создана превьюшка), и значение будет не правильным. Будет потом много гемора.

Comments

радикально. у меня тут пара сотен страниц с вызовом phpThumbOf завалялась, а на каждой по 16-30 мегабайтных картинок уже есть и переделывать их реально в ломы, проще сервер купить (что и сделано было)=) воспользуюсь информацией позже, спасибо.

А на самом деле клева получается. Во-первых, исключается человеческий фактор (не надо учить менеджеров что делать. Один раз прогнал все документы, а потом при любом сохранении документа обновляется превьюха). Во-вторых, идет как чистая статика, сервер вообще не грузится.

Жесть, слил на 0 настройки, производительность моментально улучшилась. Правильно ли я понимаю что плагин phpThumbOfCacheManager, который устанавливается вместе с самим компонентом phpThumbOf, очищает полностью папку кэша phpThumbOf? Поэтому его лучше выключать?

Правильно ли я понимаю что плагин phpThumbOfCacheManager, который устанавливается вместе с самим компонентом phpThumbOf, очищает полностью папку кэша phpThumbOf? Поэтому его лучше выключать?

Да.

Вот еще на этот компонент посмотри: http://modx.com/extras/package/phpthumbsup Райн говорит, что он гораздо быстрее работает, чем phpthumbof

Кто уже попробовал phpthumbsup? А то я довёл уже до максимума (с помощью Философа) скорость работы phpthumbof, так что у меня он как статика уже работает. Кто-то что-то скажет о phpthumbsup?

Я не пробовал еще. Но вот попробуй если что новую альтернативу от Агеля: github.com/AgelxNash/phpthumbon/releases

Охренеть, прорыв прям. Тоже еще не пробовал. Надо анонс замутить для поиска, чтобы всосало в поисковики все три альтернативы.

Пристал. Я не буду писать обзоры. Я лично могу поделиться, но не более.

Вот например: по первым моим тестам обнаружен недостаток в UP и в ON версиях (по крайней мере на этом этапе). Ни одна из них не может брать картинку с внешнего источника. OF может, то попутно обнаружена задержка в нём (проверка наличия кеша), которая составляет 1.5-3ms на одну картинку. Это тоже нехорошо.

Теперь пример, как это развивалось бы, будь это топик: вылезет какой-то умный троль и скажет, что я криворукий, и методом переделки 20 строк кода в плагине и перелопачивании 20 строк ядра всё будет работать, и что я вообще ничего не увидел, поэтому мой обзор — хламищ, мне двойка и я уволен.

Исключительно и только поэтому я никогда ничего не напишу вообще, даже если все будут аплодировать и в руках у меня будет сокровище.

Вообщем, парился я парился и надоело ON вечно кладёт файл-пустышку в стиле /assets/cache_image/noimage_100x100_31b.jpg, мол оригинал не найден, хотя оригинал есть и доступен. UP, напротив, заработал полностью, хотя выхов картинки у него совершенно другой, полностью отличается от OF. Например так: /cache/w/100/h/100/src/http://modxclub.ru/uploads/images/00/01/66/2013/07/13/avatar_48x48.jpg

это я вытащил свой аватар с твоего сайта, чтобы для примера не было моего сайта в ссылке. но смысл и расшифровка, думаю, понятны. И гибкость, на мой взгляд, приобретается просто невероятная.

А вот так я предварительно обновляю все документы, у которых еще нет сгенерированных превьюшек:

ini_set('max_execution_time', 0);
ignore_user_abort(true);

$modx->switchContext('web');
print '<pre>';

$TV_source = 2;    // Устанавливаете значения своих TV-шек
$TV_target = 17; 

$q = $modx->newQuery('modResource');
$q->innerJoin('modTemplateVarResource', 'tvimage', "tvimage.tmplvarid={$TV_source} and modResource.id = tvimage.contentid and tvimage.value != ''");
$q->leftJoin('modTemplateVarResource', 'tvprev', "tvprev.tmplvarid={$TV_target} and modResource.id = tvprev.contentid");
$q->where(array(
    'tvprev.id' => null,    
));

print "<br />Всего документов без превьюшек: ". $modx->getCount('modResource', $q);

$q->limit(100);

// Создаем самбнейл превьюшки
foreach($modx->getCollection('modResource', $q) as $resource){
    if($photo = $resource->getTVValue($TV_source) AND $thumb = $modx->runSnippet('phpthumbof', array(
        'input'     => $photo,
        'options'   => 'w=113&h=113&q=100&zc=1',
    ))
        AND $photo != $thumb
    ){
        $resource->setTVValue( $TV_target, $thumb);
        // print $thumb;
    }
    // print_r($resource->toArray());
}

Скажите пожалуйста, зачем обновлять контекст? И что делать на мультиязычных сайтах?

Скажите пожалуйста, зачем обновлять контекст?

Если вы про это:

        // На всякий случай меняем контекст, если MODX сам не поменял на контекст документа
        $origin_context = null;
        if($modx->context->key == 'mgr'){
            $origin_context = $modx->context->key;
            $modx->switchContext('web');
        }

то причина следующая: дело в том, что TV-параметры привязываются к источникам файлов (индивидуально для каждого контекста). Но нельзя указать связку TV-поля с конкретным источником файлов в контексте mgr. Если вы не используете отдельного источника файлов. используя по умолчанию filesystem (который от корня сайта работает), то вам бояться нечего. А если у вас в web-контексте другой источник (я часто в основном использую Images (/assets/images/)), то без смены контекста будет использован тот же filesystem и пути побьются, файлы просто не будут найдены.

И что делать на мультиязычных сайтах?

Мультиязычных или мультидоменных? Мультиязычность здесь никакой роли не играет, а если мультидоменный (мультиконтекстный), то используйте во всех контекстах один и тот же источник файлов, что и в web, и никаких проблем не будет.

P.S. на самом деле эта смена контекстов больше имеет значение при выполнении кода в консоли, когда по умолчанию используется текущий контекст mgr, но при редактировании документа через родной редактор страниц, проблем не должно никаких встать.

P.P.S -лучше использовать pThumb вместо phpthumbof, так как первый, в случае если картинка не найдена, использует указанный УРЛ картинки без изменения (то есть просто УРЛ будет на оригинал), в то время как второй или ничего не отдает, или записывается ошибка в TV.

Про источники файлов почитаю, спасибо.

Вы может быть не пользуетесь стандартными пакетами, но в наиболее распространённых инструкциях к Babel требуется создание отдельного контекста для языка. По-моему он иначе не работает.

И ещё, я может глупость спрошу, но все-таки: у вас в условии if с бухты-барахты появляется переменная $photo. Я как-то привык сперва объявлять переменную, класть в неё что-то, а потом уже в условии проверять.

ЗЫ вообще я phpThumbOn установил. но что-то он не больно по скорости обгоняет phpThumbof по моим наблюдениям.

Вы может быть не пользуетесь стандартными пакетами, но в наиболее распространённых инструкциях к Babel требуется создание отдельного контекста для языка.

Я мало использую стандартные пакеты, но во-первых, я знаю хорошо сам MODX (а значит и любой пакет для меня — не проблема), а во-вторых, в свое время не мало пакетов использовал. Вы в данном случае путаете разные вещи. То, что бабел заставляет делать новые контексты, это еще не говорит, что мультиязычность на что-то влияет. Повторюсь: сама мультиязычность ни на что не влияет. А вот по какой причине созданы контексты — это вообще не важно (бабел вас заставил, или сами захотели), здесь есть факт — другие контексты. И вот про эти контексты и их влияние я написал выше — здесь влияние только связка TV-контекст-Источник файлов. Всего написанного выше достаточно.

у вас в условии if с бухты-барахты появляется переменная $photo. Я как-то привык сперва объявлять переменную

У меня написано:

if($photo = $resource->getTVValue($TV_source)

В данном случае не только происходит присвоение, но и ленивое объявление (сам php ее автоматом заводит). Ни логической, ни технической ошибки здесь никакой нет. А ваше желание объявлять переменные в шапке — это ваше право. Я тоже бывает переменные объявляю, но только тогда, когда они в коде расположены далеко друг от друга. Здесь переменная используется в двух строчках кода, близко расположенных друг к другу. Какой смысл их где-то объявлять?

ЗЫ вообще я phpThumbOn установил. но что-то он не больно по скорости обгоняет phpThumbof по моим наблюдениям.

Я не говорил ставить phpThumbOn. Я говорил про pThumb.

Обнаружил такую штуку, что на выходе от сниппета phpthumbof, выходят изображения немного бледные нежели оригинал, в чем может быть проблема? Помогите очень серьезный баг на мог взгляд. Причем если даже размер ставить в настройках один в один с оригиналом, все равно обрабатывает и делает изображение превьюшки более бледной по цвету. Кто сталкивался?

{block name=params}
    {$options = "&q=100&h=360"}
{/block}
{$image = $object.image|default:$object.imageDefault}
{$src = $modx->runSnippet('phpthumbof', [
    "input" => $image,
    "options"   => $options
])}

Ну, как бы phpthumb работает через модули php. А вдруг чего не установлено или функции отключены?

Автору респект!!! В голову не приходило что так можно сделать, нарадоваться не могу. Но вот не люблю создавать лишний раз тв, пришла идея записывать превью в неиспользуемое поле, например introtext или link_attributes. Сниппету каталога не придется обращаться к тв за картинкой, тем самым еще больше ускорим загрузку. В php я не силен, сделал как смог, поправьте говнокод, буду очень признателен)

        $TV_source = 3;
        $target = 'introtext';
        $resource = & $scriptProperties['resource'];
        $photo = $resource->getTVValue($TV_source); 
        
        if($photo){
            $thumb = $modx->runSnippet('phpthumbon', array(
                'input'     => $photo,
                'options'   => 'w=113&h=115&q=100&zc=1',
            ));
            $resource->set($target, $thumb);
            $resource->save();
        }

Пожалуйста. Но вот использовать зарезервированные поля под хранение данных, не для которых они были созданы — это не правильно. Хотите в ресурс сохранять, изучайте эту статью: habrahabr.ru/post/253737/

Спасибо, Fi1osof !!!

Благодаря вам получилась работающая штуковина приведенная внизу, почему-то заработала только с phpThumbOn. У меня вопрос, можно ли теперь это как-то применить к полю Migx, пытался адаптировать различные решения найденные на различных форумах, но не выходит.

PS про php я знаю только, что он есть. все

<?php
if ($modx->event->name=="OnDocFormSave") // Плагин срабатывает при сохранении ресурса
{    
    $tv = 'image_1'; // ТВ загружаемой картинки
	$tv2 = 'image_1_preview'; // ТВ создаваемого превью

$options = 'w=100&h=100&zc=1';  

    $tvv = $resource->getTVValue($tv); // Получаем адрес загруженной картинки

    if (!empty($tvv)) // Если ТВ загружаемой картинки не пустое
    {


$thumbnail = $modx->runSnippet('phpThumbOn', array(
            'input' => $tvv,
            'options' => 'w=113&h=113&q=70&zc=1', // Параметры сохраняемой превью
        ));

	 	 if (!empty($thumbnail)){
        $resource->setTVValue($tv2, $thumbnail); // Если phpThumbOn срабтал, то записываем адрес превью в ТВ
 			}
    }
}

Не за что!

Я не очень понял вопрос по MIGX, то есть по сути его вообще не увидел. В чем именно проблема? Предположу, что на сервер MIGX-данные прилетают в JSON и вопрос в том, как выдернуть данные картинок из этой JSON-строки и провернуть операцию.

Но в любом случае, я бы рекомендовал не усложнять это. Лучше все пусть работает в штатном режиме. В производительности выигрыш небольшой, а вот подводных камней многократно прибавится. Потом не будете знать откуда ноги растут.