Разработка сайтов на UMI.CMS
Знать UMI.CMS в совершенстве невозможно.
Даже сами разработчики знают о ней не все...

UMi

Ursa Minor

Ускоряем модуль "Баннеры"

Дата публикации: 31.07.2014

Версия:2.9.5. Шаблонизатор: TPL

 

Первая мысль с баннерами - кешировать их также, как это делалось с меню и лентами новостей. К сожалению, в этом случае не будет собираться статистика по показам, а рандомные баннеры начнут вести себя несколько странно.  Именно статистика была причиной того, что отказались от статического кеширования средствами nginx, хотя оно было самое производительное и позволило бы немало съэкономить на хостинге.

 

Для начала попробуем найти  узкое место.

Даже при беглом просмотре бросается в глаза нелогичная конструкция:



			$sel = new selector("objects");
			$sel->types("object-type")->name("banners", "banner");
			$sel->where("show_start_date")->less(time());
			$sel->where("is_active")->equals(1);
			$sel->where('place')->equals($placeId);
			$sel->where('view_pages')->equals($currentPageId);
			$result = $sel->result();
			
			
			$selWithoutNull = new selector("objects");
			$selWithoutNull->types("object-type")->name("banners", "banner");
			$selWithoutNull->where("show_start_date")->less(time());
			$selWithoutNull->where("is_active")->equals(1);
			$selWithoutNull->where('place')->equals($placeId);
			$selWithoutNull->where('view_pages')->equals($currentPageParentsIds);
			$resultWithoutNull = $selWithoutNull->result();
		

			$selWithNull = new selector("objects");
			$selWithNull->types("object-type")->name("banners", "banner");
			$selWithNull->where("show_start_date")->less(time());
			$selWithNull->where("is_active")->equals(1);
			$selWithNull->where('place')->equals($placeId);
			$selWithNull->where('view_pages')->isnull(true);
			$resultWithNull = $selWithNull->result();

 

Ежу видно, что три подряд идущих выборки отличаются только одной строкой: проверкой значения поля view_pages

Методом query() объекта selector  выводим на экран исходный текст запроса и ахаем - три запроса  с шеститабличным соединением!  Нечего удивляться, что баннеры тормозили главную страницу на 30%!

 

Придется переписать запрос  вручную.

SELECT  
	o.id as id 
	FROM 
		cms3_object_types t, 
		cms3_objects o 
		LEFT JOIN 
			cms3_object_content oc_282_lj 
				ON oc_282_lj.obj_id=o.id 
				AND oc_282_lj.field_id = '282' 
		LEFT JOIN 
			cms3_object_content oc_275_lj 
				ON oc_275_lj.obj_id=o.id 
				AND oc_275_lj.field_id = '275' 
		LEFT JOIN 
			cms3_object_content oc_286_lj 
				ON oc_286_lj.obj_id=o.id 
				AND oc_286_lj.field_id = '286' 
		LEFT JOIN 
			cms3_object_content oc_285_lj 
				ON oc_285_lj.obj_id=o.id 
				AND oc_285_lj.field_id = '285' 
	WHERE 
		o.type_id IN (80, 81, 82, 103, 83) 
		AND t.id = o.type_id 
		AND (
			oc_282_lj.int_val < {$текущее_время} 
			AND oc_275_lj.int_val = '{$текущая страница}' 
			AND oc_286_lj.rel_val = '{$место_размещения_баннера}' 
			AND ( 
				oc_285_lj.tree_val = '1' 
				OR  oc_285_lj.tree_val IN({$список родительских страниц})  
				OR  oc_285_lj.tree_val IS NULL 
			)
		);

 

Казалось бы, теперь все будет хорошо. Как обычно, берем целиком имеющийся макрос, переносим его в __custom.php.  Все обращения к $this заменяем на объект модуля. Запускаем и...  облом(

Макрос ссылается на статическую переменную arrVisibleBanners. Естественно, из подключаемого абстрактного класса к ней нет доступа.  Аналогично,  методы getPlaceId и renderBanner  объявлены как protected и к ним доступа тоже нет.

 

Напомню, что начиная с 2.8 в ЮМИ в кастомных макросах  обращаться к основному классу нужно не через $this, а через объект модуля. Т.о., строки вида:

self::renderBanner($iBannId);
$this->getPlaceId($sPlace);

следует заменить на:

$module = cmsController::getInstance()->getModule('banners');// получили объект модуля

$module->renderBanner($iBannId);
$module->getPlaceId($sPlace);

Увы, со static и protected  это не прокатывает.

Решением может быть хитрый финт ушами. Призовем на помощь механизм отражений, встроенный в php, и доберемся до нужных свойств в обход.  Я использовал  PHP 5.4.4-14+deb7u12, но и с 5.3, скорее всего, будет работать.  Объект-отражение содержит в себе информацию о классе, имя которого передано ему к конструктор.

В итоге доступ к защищенным параметрам будет таким:



$module = cmsController::getInstance()->getModule('banners'); // получили модуль

$ReflectedClass = new ReflectionClass($module); //получили объект-отражение

// Работа с методами через отражение

$renderBanner = $ReflectedClass->getMethod('renderBanner'); // получили protected-метод "renderBanner" модуля Баннеры
$renderBanner->setAccessible(true);  // сделали его доступным

//ниже показано как теперь обращаться к полученному методу:
$sResult = $renderBanner->invoke($module, параметр_1, параметр_2, ....);


// Работа со статическими переменными через отражение

$arrVisibleBanners = $ReflectedClass->getStaticPropertyValue('arrVisibleBanners'); 
// прочитали значение статической переменной arrVisibleBanners из модуля Баннеры и скопировали ее локально для работы.
// а вот так записали измененное значение обратно в модуль
$ReflectedClass->setStaticPropertyValue('arrVisibleBanners', $arrVisibleBanners);

 

Теперь сводим все воедино и получаем кастомный макрос:



	public function insertCache($sPlace = "", $iMacrosID=0, $bList = false, $current_element_id = false) {

		$module = cmsController::getInstance()->getModule('banners');
		$ReflectedClass = new ReflectionClass($module);
		
		$getPlaceId = $ReflectedClass->getMethod('getPlaceId');
		$getPlaceId->setAccessible(true);
		
		$renderBanner = $ReflectedClass->getMethod('renderBanner');
		$renderBanner->setAccessible(true);
		
		$arrVisibleBanners = $ReflectedClass->getStaticPropertyValue('arrVisibleBanners');
			
		if($current_element_id) {
			$currentPageId = $current_element_id;
		} else {
			$currentPageId = cmsController::getInstance()->getCurrentElementId();
		}			
		
		$places = $getPlaceId->invoke($module, $sPlace);

		if (!count($places)) return "";
				
		$placeId = $places[0];
		$place = umiObjectsCollection::getInstance()->getObject($placeId);
		$bShowRandomBanner = (bool) $place->getValue('is_show_rand_banner');

		$sResult = "";
		$arrBannersList = array();
		
		$currentPageParentsIds = umiHierarchy::getInstance()->getAllParents($currentPageId);
		
		$time = time();
		$parents = implode(',',$currentPageParentsIds);
		if(!$parents) $parents = 0;
	
		
$sql = <<<EOD
SELECT  o.id as id FROM cms3_object_types t, cms3_objects o LEFT JOIN cms3_object_content oc_282_lj ON oc_282_lj.obj_id=o.id AND oc_282_lj.field_id = '282' LEFT JOIN cms3_object_content oc_275_lj ON oc_275_lj.obj_id=o.id AND oc_275_lj.field_id = '275' LEFT JOIN cms3_object_content oc_286_lj ON oc_286_lj.obj_id=o.id AND oc_286_lj.field_id = '286' LEFT JOIN cms3_object_content oc_285_lj ON oc_285_lj.obj_id=o.id AND oc_285_lj.field_id = '285' WHERE o.type_id IN (80, 81, 82, 103, 83) AND t.id = o.type_id AND (oc_282_lj.int_val < {$time} AND oc_275_lj.int_val = '{$currentPageId}' AND oc_286_lj.rel_val = '{$placeId}' AND ( oc_285_lj.tree_val = '1' OR  oc_285_lj.tree_val IN({$parents})  OR  oc_285_lj.tree_val IS NULL ));
EOD;


		$o = umiObjectsCollection::getInstance();
		$arrSelResults = array();
		$res = l_mysql_query($sql);
		while($row = mysql_fetch_assoc($res)){
			$id = $row['id'];
			
			$oBanner = $o->getObject($id);
			$arrSelResults[] = $oBanner;
		}


		mysql_free_result($res);


		// others filters =========================================================

		if((defined("DB_DRIVER") && DB_DRIVER != "xml")||!defined("DB_DRIVER"))
		if ($oStat = cmsController::getInstance()->getModule("stat")) {
			$arrGetTags = $oStat->getCurrentUserTags();
		}

		foreach ($arrSelResults as $id => $oNextBanner) {				

			if ($oNextBanner instanceof umiObject) {
				
				$iNextBanId = $oNextBanner->getId();					
				
				//echo $iNextBanId.'
'; if (!in_array($currentPageId, $oNextBanner->getValue('not_view_pages'))) { // max count views filter if ($oNextBanner->getValue('max_views') <= 0 || $oNextBanner->getValue('views_count') <= $oNextBanner->getValue('max_views')) { $bShowActual = true; $weight = 1; // tags filter $arrBannerTags = $oNextBanner->getValue("tags"); if (count($arrBannerTags)) { $iCurrPageId = cmsController::getInstance()->getCurrentElementId(); $oCurrPage = umiHierarchy::getInstance()->getElement($iCurrPageId, true); if(is_object($oCurrPage)) { $arrPageTags = $oCurrPage->getValue("tags"); } else { $arrPageTags = Array(); } $arrCommonTags = array_intersect($arrBannerTags, $arrPageTags); if (!count($arrCommonTags)) { $bShowActual = false; } else { $weight += count($arrCommonTags); } } // do show till filter $oShowTillDate = $oNextBanner->GetValue('show_till_date'); if ($oShowTillDate instanceof umiDate && $oShowTillDate->timestamp) { if ($oShowTillDate->timestamp < $oShowTillDate->getCurrentTimeStamp()) { $bShowActual = false; } } if ($bShowActual) { // time-targeting filter ======================= if ($oNextBanner->getValue('time_targeting_is_active')) { $oRanges = new ranges(); // by month $sByMonth = $oNextBanner->getValue('time_targeting_by_month'); if (strlen($sByMonth)) { $iCurrMonth = (int) date("m"); $arrShowByMonth = $oRanges->get($sByMonth, 1); if (array_search($iCurrMonth, $arrShowByMonth)===false) $bShowActual = false; } // by month days $sByMonthDays = $oNextBanner->getValue('time_targeting_by_month_days'); if (strlen($sByMonthDays) && $bShowActual) { $iCurrMonthDay = (int) date("d"); $arrShowByMonthDays = $oRanges->get($sByMonthDays); if (array_search($iCurrMonthDay, $arrShowByMonthDays)===false) $bShowActual = false; } // by week days $sByWeekDays = $oNextBanner->getValue('time_targeting_by_week_days'); if (strlen($sByWeekDays) && $bShowActual) { $iCurrWeekDay = (int) date("w"); $arrShowByWeekDays = $oRanges->get($sByWeekDays); if (array_search($iCurrWeekDay, $arrShowByWeekDays)===false) $bShowActual = false; } // by hours $sByHours = $oNextBanner->getValue('time_targeting_by_hours'); if (strlen($sByHours) && $bShowActual) { $iCurrHour = (int) date("G"); $arrShowByHours = $oRanges->get($sByHours); if (array_search($iCurrHour, $arrShowByHours)===false) $bShowActual = false; } } // user tags filter if ($bShowActual) { $arrBannerTags = $oNextBanner->getValue("user_tags"); if (is_array($arrBannerTags) && count($arrBannerTags)) { if ($oStat = cmsController::getInstance()->getModule("stat")) { $arrUserTags = array(); $refererTags = array(); $referer = getSession("http_referer"); if ($referer && $referer == getServer('HTTP_REFERER') && !regedit::getInstance()->getVal("//modules/banners/not-use-referer-tags")) { $refererTags = $module->getRefererTags($referer); } if (isset($arrGetTags) && is_array($arrGetTags)) { foreach ($arrGetTags as $sTmp => $arrTagInfo) { if (isset($arrTagInfo)) $arrUserTags[] = $arrTagInfo["tag"]; } } $iAllowTags = 0; for ($nI=0; $nI < count($arrBannerTags); $nI++) { $sNextTag = $arrBannerTags[$nI]; if (in_array($sNextTag, $arrUserTags)) { $iAllowTags++; } if (in_array($sNextTag, $refererTags)) { $iAllowTags +=5; } } if (!$iAllowTags) { $bShowActual = false; } else { $weight += $iAllowTags; } } } } if($bShowActual) { $bTargetingActive = $oNextBanner->getValue("city_targeting_is_active"); if($bTargetingActive) { $sBannerCity = $oNextBanner->getValue("city_targeting_city"); if ($sBannerCity) { if($oGeoIPMod = cmsController::getInstance()->getModule("geoip")) { $info = $oGeoIPMod->lookupIp(getServer('REMOTE_ADDR')); if (isset($info['city'])) { $sCurrentCity = $info['city']; $city = umiObjectsCollection::getInstance()->getObject($sBannerCity)->getName(); $bShowActual = ($sCurrentCity == $city); } else $bShowActual == false; } } } } if ($bShowActual) { $arrBannersList[$iNextBanId] = $weight; } } else { } } else { } } } } //exit; if (count($arrBannersList)) { $iShowBanId = 0; arsort($arrBannersList); $arrBannersList = array_keys($arrBannersList); if (count($arrBannersList) > 1) { foreach ($arrVisibleBanners as $sNextPlace => $arrPlaceBanners) { $arrBannersList = array_diff($arrBannersList, $arrPlaceBanners); } } if ($bShowRandomBanner) { // random banner srand((float) microtime() * 10000000); $iRandBanInd = array_rand($arrBannersList); if(isset($arrBannersList[$iRandBanInd])) $iShowBanId = $arrBannersList[$iRandBanInd]; } else { reset($arrBannersList); $iShowBanId = current($arrBannersList); } if ($bList) { $params = array(); $params['nodes:banners'] = array(); foreach ($arrBannersList as $itmp => $iBannId) { $arrVisibleBanners[$sPlace][] = $iBannId; $ReflectedClass->setStaticPropertyValue('arrVisibleBanners', $arrVisibleBanners); $sResult = $renderBanner->invoke($module, $iShowBanId); } $sResult = def_module::parseTemplate("", $params); } else { $arrVisibleBanners[$sPlace][] = $iShowBanId; $ReflectedClass->setStaticPropertyValue('arrVisibleBanners', $arrVisibleBanners); $sResult = $renderBanner->invoke($module, $iShowBanId); } } $reg = regedit::getInstance(); if (($reg->getVal("//modules/banners/days-before-notification") || $reg->getVal("//modules/banners/clicks-before-notification")) && $reg->getVal("//modules/banners/last-check-date") < (time()-3600*24)) { $module->sendNotification(); } return $sResult; }

 

Ускорение достигается за счет трехкратного уменьшения количества тяжелых запросов.  По идее, можно еще увеличить производительность, кешируя объекты баннеров, но мне пока и так хватило.

 

 

 

Новости

30.10.2016

Обновлено описание макроса для вывода всех элементов справочника UMI.CMS с учетом версии 14.

 

21.07.2015

Памятка по обновлению до debian 8.1. Приятно вкусить все плюшки PHP 5.6

11.11.2014

Как выполнить SQL-запрос из bash-скрипта. Памятка

11.11.2014

Как отследить нагрузку сайта на сервер.

06.11.2014

Заготовка для галереи изображений с прокруткой. Не знаю, когда дойдут руки довести ее до вида плагина, поэтому открываю как есть. Можно доработать.

05.11.2014

Выкладываю как заготовку свой слайдер изображений на mootools

Все обновления