Сообщение

Overriding и Observer/Notifier

Overriding и Observer/Notifier. Пример практического применения


Цель настоящей небольшой заметки - привести практический пример изменения функциональности в Zen-Cart не изменяя при этом кода ядра.

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

Итак, несколько слов о способе решения данной задачи.

Как известно, индексная страница (main_page=index) имеет три варианта отображения в зависимости от параметров запроса. Это варианты: 'top', 'nested' и 'products'. Наша цель - вместо варианта 'nested' отобразить вариант 'products'. Но это еще не все. Кроме этого нужно изменить запрос, которы выбирает товары для отображения так, чтобы он выбирал товары не только из текущей категории, но и из всех ее подкатегорий.

При "классическом" способе решения задачи нужно:

1. В файле includes/modules/pages/index/header_php.php
вместо фрагмента

if ($category_parent->fields['total'] > 0) {
      $category_depth = 'nested';
 } else {
      $category_depth = 'products';
 }

оставить всего-лишь

$category_depth = 'products';

2. В файле includes/index_filters/default_filter.php
фрагмент

// show the products in a given category
    if (isset($_GET['filter_id']) && zen_not_null($_GET['filter_id'])) {
// We are asked to show only specific category
      $listing_sql = "select " . $select_column_list . " p.products_id, p.products_type, p.manufacturers_id, p.products_price, p.products_tax_class_id, pd.products_description, IF(s.status = 1, s.specials_new_products_price, NULL) as specials_new_products_price, IF(s.status = 1, s.specials_new_products_price, p.products_price) as final_price, p.products_sort_order, p.product_is_call, p.product_is_always_free_shipping, p.products_qty_box_status
      from " . TABLE_PRODUCTS . " p left join " . TABLE_SPECIALS . " s on p.products_id = s.products_id, " .
      TABLE_PRODUCTS_DESCRIPTION . " pd, " .
      TABLE_MANUFACTURERS . " m, " .
      TABLE_PRODUCTS_TO_CATEGORIES . " p2c
      where p.products_status = 1
        and p.manufacturers_id = m.manufacturers_id
        and m.manufacturers_id = '" . (int)$_GET['filter_id'] . "'
        and p.products_id = p2c.products_id
        and pd.products_id = p2c.products_id
        and pd.language_id = '" . (int)$_SESSION['languages_id'] . "'
        and p2c.categories_id = '" . (int)$current_category_id . "'" .
        $alpha_sort;
    } else {
// We show them all
      $listing_sql = "select " . $select_column_list . " p.products_id, p.products_type, p.manufacturers_id, p.products_price, p.products_tax_class_id, pd.products_description, IF(s.status = 1, s.specials_new_products_price, NULL) as specials_new_products_price, IF(s.status =1, s.specials_new_products_price, p.products_price) as final_price, p.products_sort_order, p.product_is_call, p.product_is_always_free_shipping, p.products_qty_box_status
       from " . TABLE_PRODUCTS_DESCRIPTION . " pd, " .
       TABLE_PRODUCTS . " p left join " . TABLE_MANUFACTURERS . " m on p.manufacturers_id = m.manufacturers_id, " .
       TABLE_PRODUCTS_TO_CATEGORIES . " p2c left join " . TABLE_SPECIALS . " s on p2c.products_id = s.products_id
       where p.products_status = 1
         and p.products_id = p2c.products_id
         and pd.products_id = p2c.products_id
         and pd.language_id = '" . (int)$_SESSION['languages_id'] . "'
         and p2c.categories_id = '" . (int)$current_category_id . "'" .
         $alpha_sort;
    }

заменить на

    $sca = array();
    zen_get_subcategories(&$sca, (int)$current_category_id);
    $scal = (int)$current_category_id;
     
    for ($i = 0; $i < sizeof($sca); $i++) {
      $scal .= ', ' . $sca[$i];
    }

// show the products in a given category
    if (isset($_GET['filter_id']) && zen_not_null($_GET['filter_id'])) {
// We are asked to show only specific category
      $listing_sql = "select " . $select_column_list . " p.products_id, p.products_type, p.manufacturers_id, p.products_price, p.products_tax_class_id, pd.products_description, IF(s.status = 1, s.specials_new_products_price, NULL) as specials_new_products_price, IF(s.status = 1, s.specials_new_products_price, p.products_price) as final_price, p.products_sort_order, p.product_is_call, p.product_is_always_free_shipping, p.products_qty_box_status
      from " . TABLE_PRODUCTS . " p left join " . TABLE_SPECIALS . " s on p.products_id = s.products_id, " .
      TABLE_PRODUCTS_DESCRIPTION . " pd, " .
      TABLE_MANUFACTURERS . " m, " .
      TABLE_PRODUCTS_TO_CATEGORIES . " p2c
      where p.products_status = 1
        and p.manufacturers_id = m.manufacturers_id
        and m.manufacturers_id = '" . (int)$_GET['filter_id'] . "'
        and p.products_id = p2c.products_id
        and pd.products_id = p2c.products_id
        and pd.language_id = '" . (int)$_SESSION['languages_id'] . "'
        and p2c.categories_id in (" . $scal . ")" .
        $alpha_sort;
    } else {
// We show them all
    
      $listing_sql = "select " . $select_column_list . " p.products_id, p.products_type, p.manufacturers_id, p.products_price, p.products_tax_class_id, pd.products_description, IF(s.status = 1, s.specials_new_products_price, NULL) as specials_new_products_price, IF(s.status =1, s.specials_new_products_price, p.products_price) as final_price, p.products_sort_order, p.product_is_call, p.product_is_always_free_shipping, p.products_qty_box_status
       from " . TABLE_PRODUCTS_DESCRIPTION . " pd, " .
       TABLE_PRODUCTS . " p left join " . TABLE_MANUFACTURERS . " m on p.manufacturers_id = m.manufacturers_id, " .
       TABLE_PRODUCTS_TO_CATEGORIES . " p2c left join " . TABLE_SPECIALS . " s on p2c.products_id = s.products_id
       where p.products_status = 1
         and p.products_id = p2c.products_id
         and pd.products_id = p2c.products_id
         and pd.language_id = '" . (int)$_SESSION['languages_id'] . "'
         and p2c.categories_id in (" . $scal . ")" .
         $alpha_sort;
    }

Теперь сделаем это при помощи имеющихся в Zen-Cart механизмов: Overriding и Observer/Notifier System.

1. В конце выполнения header_php.php генерируется уведомление

$zco_notifier->notify('NOTIFY_HEADER_END_INDEX');

Для решения наше задачи создадем класс, который при получении данного уведомления изменял бы значение переменной $category_depth с 'nested' на 'products'.

Назовем его productsListModifier и поместим в файл includes/classes/observers/class.productsListModifier.php.

class productsListModifier extends base {
  function productsListModifier() {
    global $zco_notifier;
    $zco_notifier->attach($this, array('NOTIFY_HEADER_END_INDEX'));
  }
  
  function update(&$callingClass, $eventID, $paramsArray) {
    global $category_depth, $language_page_directory; 
    global $template_dir_select;
    
    if ($eventID == 'NOTIFY_HEADER_END_INDEX') {
      if ($category_depth == 'nested') {
        $category_depth = 'products';
        require($language_page_directory . $template_dir_select . 'index.php');
      }
    }
  }
}

Как видим все очень просто. В конструкторе мы присоединяем ссылку на наш объект с списку наблюдателей, которые будут оповещаться о событии 'NOTIFY_HEADER_END_INDEX'. При наступлении события, будет вызван метод (функция) update() нашего объекта, который и выполнит всю необходимую работу

Для того, чтобы объект был создан, поместим соответствующие "указания" для системы автолоадинга. Для этого в каталоге includes/auto_loaders поместим файл config.productsListModifier.php следующего содержания

.
<?php
$autoLoadConfig[200][] = array('autoType'=>'class',
                              'loadFile'=>'observers/class.productsListModifier.php');
$autoLoadConfig[200][] = array('autoType'=>'classInstantiate',
                              'className'=>'productsListModifier',
                              'objectName'=>'productsListModifier');
?>

Первый пункт нашей задачи решен.

2. К сожалению, во время выполнения кода из файла default_filter.php уведомления не генерируются. Поэтому, чтобы изменить его функциональность "предложим" ядру вместо него выполнять измененный нами файл. Для этого используем возможности Overriding'а.

Просто поместим модифицированный файл в каталог index_filter/classic. "classic" - это название текущего шаблона. Теперь вместо index_filter/default_index.php будет загружен index_filter/classic/default_index.php. Как видим, опять все просто.

Задача решена.

Заметьте, при решении задачи нами не был модифицирован ни один файл ядра.

Это значит, что:

  • для удаления нового функционала достаточно просто удалить добавленные нами файлы. (Автоматизироввать процесс инсталляции-деинсталляции дополнени в данном случае куда проще, чем в случае osCommerce)
  • при выходе новой версии скрипта (а для Zen-Cart они выходят относительно часто) нам не прийдется опять вносить изменения в файлы скрипта так, как неприменно пришлось бы в случае osCommerce

Эти и другие не менее важные моменты, заложенные в архитектуре и, на первый вгляд, не видные "обычному" пользователю позволяют существенно снизить стоимость владения магазином.

В этой заметке мы не касались вопросов относительно внутреннего устрйства систем Overriding и Observer/Notifier. Все интересующиеся могу ознакомиться с ними на страницах официальной документации


Joomla templates by a4joomla