Тест вложенности ресурса в контейнер [с блекджеком и плюхами]


Проверить, есть ли контейнер в списке родителей ресурса, можно разными способами:

  • Можно сделать это используя карту ресурсов, которую MODX создаёт для каждого контекста после обновления кеша. Для этого необходимо воспользоваться методом $modx->getParentIds. Таким образом нагрузка и время затрачиваемое на проверку будет минимальным.

  • Не заморачиваться и сделать всё исключительно на Fenom, без модификаторов. Тут мы тоже можем воспользоваться методом $_modx->getParentIds, который Василий услужливо вынес в список доступных в Fenom.

  • Можно даже извратиться до того, что на каждую такую проверку получать объект через getObject('modResource') + получать объекты его родителей через getOne('Parent'). К слову, что будет в случае, если нам, к примеру, надо проверить 50 товаров на странице на вложенность в определённую категорию?
    Ради интереса, я воспроизвёл подобную ситуацию на тестовом сайте Modhost:
    1. Без каких-либо проверок 50 товаров в списке категории выводятся за 0,0282 сек.
    2. С подобной проверкой, за 0.1304 сек.
    3. С проверкой, которую мы рассматриваем в этом посте, за 0.0307 сек.

    А потом заказчики сходят с ума, почему их сайт так долго открывается...

Поймите меня правильно

Дело даже не столько в единичном сниппете, сколько в самом подходе к разработке. Если для исполнителя нет ничего страшного в подобном сниппете, который прибавляет 0.1 секунду ко времени загрузки страницы при определённых условиях, то заказчику потом приходится тратить в двое больше на оптимизацию сайта или жить с тем, что есть.

Преимущества данного решения

В чём же преимущества данного решения в отличие, к примеру, от этого:
  1. Скорость работы и низкая нагрузка на сервер,
  2. Возможность передавать опции, вроде ключа контекста,
  3. Возможность указать уровень вложенности проверки,
  4. Основной параметр проверки (id контейнера) не вшит в сниппет, а указывается извне.

Сниппет-модификатор проверки вложенности ресурса в контейнер

Каким же должен быть правильный сниппет-модификатор проверки вложенности ресурса в контейнер:
// Parent
$parent = ($options['parent'] ?: null);
unset($options['parent']);

if (empty($input) || empty($parent)) {
    return null;
}

// Height
$height = ($options['height'] ?: 10);
unset($options['height']);

// Alt keys
foreach (array(
    'context_key' => 'context',
    'ctx' => 'context',
) as $k => $v) {
    if (isset($options[$k])) {
        $options[$v] = $options[$k];
    }
}

if (!$parents = $modx->getParentIds($input, $height, $options)) {
    return null;
}

return in_array($parent, $parents) ?: null;
Создаём сниппет inParent с этим кодом.

Пример использования

Использовать его до безобразия просто:
{if ($_modx->resource['id'] | inParent : [
    'parent' => 2,
    'height' => 3,
    'ctx' => 'web',
])?}
    Родитель с id 2 найден на 3 уровня вверх в контексте web.
{else}
    Не найден.
{/if}

P.S.: За то, что так скрупулёзно отношусь к нагрузке на сервер и скорости работы скрипта, спасибо Михаилу Воеводскому, что ткнул носом однажды. =)
26 апреля 2017, 08:28    Павел Гвоздь   
16    554 +15

Комментарии (15)

  1. Николай 26 апреля 2017, 09:37 # +4
    Спасибо за труд, очень правильно, что написали.
    Сам внимательно отношусь к нагрузке на сервер и скорости загрузки, т.к. как покупатель не люблю тормозные сайты.

    Сильно не хватает таких постов с разбором внутренних механизмов кеширования модх.

    Хотя про getParentids и getchildIds все должны знать.

    Есть еще полезный findResource
    В вот так нужный getResourceURI так и не появился(( forums.modx.com/index.php/topic,58207.msg332426.html
    1. Комментарий был удален.
      1. Комментарий был удален.
        1. Комментарий был удален.
          1. Комментарий был удален.
          2. Комментарий был удален.
            1. Комментарий был удален.
              1. Комментарий был удален.
        2. Николай 27 апреля 2017, 09:55 # +4
          Не стал создавать отдельный топик.

          Вот пример как делают настоящие хардкорные программисты для получения тайтла родителя верхнего уровня:

          if(!$level_1){
                          	$parentID = @$modx->resource->get('parent');
              				$document = @$modx->getObject('modResource',array(
                  				'id' => $parentID,
              				));
                          	$parentID = @$document->get('parent');
                  			$document = @$modx->getObject('modResource',array(
                      			'id' => $parentID,
                  			));
                  			$parentID = @$document->get('parent');
                  			$document_parent = @$modx->getObject('modResource',array(
                      			'id' => $parentID,
                  			));
                  			$level_1="";
                  			if($document_parent) {
                      			$level_1 = $document_parent->get('pagetitle');
                      			$level_1= mb_strtolower($level_1, 'UTF-8');
                  			};
                          };
          Сегодня получил этот код от своих тру сеошников
          1. Сергей Лелеко 27 апреля 2017, 11:48 # +2
            Ничего себе у вас SEOшники код пишут ))
            1. Евгений Шеронов 27 апреля 2017, 12:00 # 0
              Но это опять же долгий способ с получением нескольких объектов, да ещё и не универсальный.
              Хотя даже с получением объектов можно было сделать цикл, где выискивался бы самый верхний родитель.

              Подходить лишь для такой структуры:
              Родитель 1
              — Родитель 2
              — Родитель 3
              — Ресурс

              P.S. Как я думаю, супер скорость будет при getParentIds и запроса newQuery с лимитом 1 и выборкой pagetitle.
              1. Николай 27 апреля 2017, 16:47 # 0
                Я как антипример и дал.
                Вот кусочек кода, который сам использую для вытаскивания всех тайтлов родителей просто для примера:
                $parent_ids=$this->modx->getParentIds($id, $level, array('context' => $context));
                      $where = array(
                        'id' => $parentIds
                        );
                        array_push($parent_ids,$id); 
                        if(is_array($parent_ids)&&count($parent_ids)>0) {
                            $p_query = $this->modx->newQuery('modResource');
                            $p_query->select(array('id','pagetitle'));
                            $p_query->distinct();
                            $p_query->sortby('id', 'asc');
                            $p_query->where(array('id:IN' => $parent_ids));            
                            if ($p_query->prepare() && $p_query->stmt->execute()){     
                                $p_out= array();        
                               // $this->modx->log(modX::LOG_LEVEL_ERROR, $p_query->toSql());        
                                $p_out=$p_query->stmt->fetchAll(PDO::FETCH_ASSOC);                
                                //$this->modx->log(modX::LOG_LEVEL_ERROR, $p_out); 
                                $titles_array=array();
                               
                                foreach ($p_out as $val){                
                                 $titles_array[]=$val['pagetitle'];         
                                }
                            }
                        }
                Сейчас хочу в функцию его завернуть, чтобы можно было конкретный тайтл конкретного родителя с нужного уровня тащить, например.
            2. Сергей Шлоков 27 апреля 2017, 18:01 # +4
              Внесу свои 5 копеек в оптимизацию кода.

              1. Самая первая переменная называется $parent, а проверяется $parent_id. Явная опечатка.
              2. Блок с Alt key непонятно зачем нужен. Достаточно просто указать ключ context вместо ctx и context_key. Думаю, программисту это будет не сложно.
              3. В коде
              if (!$parents = $modx->getParentIds($input, $height, $options)) {
                  $parents = array();
              }
              можно сразу возвращать null. Зачем тратить время на дальнейшие операции.

              П.С. Ещё желательно, чтобы переменная height была необязательная. Она явно ограничивает применение фильтра.
              1. Павел Гвоздь 27 апреля 2017, 18:04 # 0
                1, 3 — полностью согласен!
                2 — это для личного удобства. Постоянно путаю, где указать context_key, а где context.
                1. Павел Гвоздь 27 апреля 2017, 18:06 # 0
                  Скорректировал код, спасибо! Приятно, когда твой код кто-то читает. ;)

                  П.С. Ещё желательно, чтобы переменная height была необязательная. Она явно ограничивает применение фильтра.
                  Тут не понял. Разве сейчас она обязательна?
                Вы должны авторизоваться, чтобы оставлять комментарии.