Динамическое обновление цены товара miniShop2 по курсу доллара, через Cron.
Всем привет, друзья. Возникла у меня задача, сделать так, чтобы цены у товаров менялись в соответствии с курсом доллара, раз в сутки. Оговорюсь, что это мой способ, если вы знаете лучше, то буду рад открыть для себя еще варианты… И так, приступим.
Создаем системную опцию MODX с ключём currency — в нее будет приходить наш курс доллара.
Устанавливаем дополнение CronManager, с помощью которого мы будем получать курс и обновлять цену раз в день.
Создаем TV поле с названием usdprice, в котором мы будем писать цену в долларах, в идеале конечно добавить поле в сам товар, но и так почему бы и нет :) И не забываем указать ему шаблон товара.
Создаем сниппет с любым названием, у меня это будет getCurrency и помещаем в него следующий код, этот сниппет будет получать курс доллара на сегодняшний день:
Далее создаем еще сниппет, например с названием updatePrice, он будет обновлять цену товаров на основе существующего курса.
Вставляем в него этот код:
Когда мы все подготовили, нам нужно это дело заставить запускаться раз в сутки, ведь ЦБ обновляет курс раз в день, так, что я не вижу смысла это делать чаще.
Чтобы выполнять наши сниппеты по расписанию, нам понадобится Cron, а именно, дополнение CronManager, настраивается он очень легко, вот документация.
Теперь дело осталось за малым. Открываем в админке CronManager, и нажимаем кнопку Create new cronjob и выбираем наши два плагина и выставляем время запуска в минутах.
ВАЖНО, сначала должен запускаться сниппет получения курса доллара, а потом сниппет обновления товаров иначе товары будут обновляться раньше, чем получат новый курс.
У товаров мы заранее проставляем цену в долларах.
Далее сниппет getCurrency получает из ЦБ курс на сегодняшний день и помещает его в системную настройку currency.
Затем сниппет updatePrice проходится по всем товарам и ищет цену в долларах, если она стоит, то пересчитывает цену в рублях по сегодняшнему курсу и подставляет уже новую цену в товар, в поле price.
Вот такой функционал у меня получился, я уверен, что есть масса вариантов как это сделать и мой один из них.
Так как я новичок в сфере программирования и хоть как-то пытаюсь принести пользу, Я надеюсь, что найдутся люди, которым моя статья поможет, всем спасибо за внимание :)
Подготовка
Создаем системную опцию MODX с ключём currency — в нее будет приходить наш курс доллара.
Устанавливаем дополнение CronManager, с помощью которого мы будем получать курс и обновлять цену раз в день.
Создаем TV поле с названием usdprice, в котором мы будем писать цену в долларах, в идеале конечно добавить поле в сам товар, но и так почему бы и нет :) И не забываем указать ему шаблон товара.
Создаем сниппет с любым названием, у меня это будет getCurrency и помещаем в него следующий код, этот сниппет будет получать курс доллара на сегодняшний день:
<?php
//Получаем нашу системную опцию с курсом доллара
$settings = $modx->getObject('modSystemSetting', 'currency');
//Подключаемся к ЦБРФ и тянем у них файл с ежедневными курсами валют
function getValutes($url) {
$ch = curl_init();
curl_setopt($ch,CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_FAILONERROR,1);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION,1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch, CURLOPT_TIMEOUT, 15);
$retValue = curl_exec($ch);
curl_close($ch);
return $retValue;
}
$get = getValutes('http://www.cbr.ru/scripts/XML_daily.asp');
if ($get){
$xml = new SimpleXMLElement($get);
$currency = 0;
// Парсим приходящий XML и вытаскиваем курс той валюты, которая нам нужна
foreach ($xml->Valute as $val) {
if ($val->attributes()['ID'] == 'R01235'){
$currency = $val->Value;
};
}
// Помещаем наш курс в системную опцию 'currency' и сохраняем
$set = $settings->set('value', $currency);
$settings->save();
$modx->cacheManager->refresh(array('system_settings' => array()));
$get = $settings->get('value');
}
Далее создаем еще сниппет, например с названием updatePrice, он будет обновлять цену товаров на основе существующего курса.
Вставляем в него этот код:
<?php
// Получаем курс из системной опции
$currency = $modx->getOption('currency');
$currency = str_replace(',','.',$currency);
// Получаем все товары
$col = $modx->getCollection('msProduct');
foreach ($col as $value){
// Если есть цена в долларах, то рассчитываем цену по курсу и помещаем новую цену в поле price
if ($value->getTVValue('usdprice')) {
$price = $value->get('price');
$usd = $value->getTVValue('usdprice');
$sum = $currency * $usd;
$sum = floor($sum);
$set = $value->set('price', $sum);
$value->save();
}
}
Запуск по расписанию
Когда мы все подготовили, нам нужно это дело заставить запускаться раз в сутки, ведь ЦБ обновляет курс раз в день, так, что я не вижу смысла это делать чаще.
Чтобы выполнять наши сниппеты по расписанию, нам понадобится Cron, а именно, дополнение CronManager, настраивается он очень легко, вот документация.
Теперь дело осталось за малым. Открываем в админке CronManager, и нажимаем кнопку Create new cronjob и выбираем наши два плагина и выставляем время запуска в минутах.
ВАЖНО, сначала должен запускаться сниппет получения курса доллара, а потом сниппет обновления товаров иначе товары будут обновляться раньше, чем получат новый курс.
Как это работает
У товаров мы заранее проставляем цену в долларах.
Далее сниппет getCurrency получает из ЦБ курс на сегодняшний день и помещает его в системную настройку currency.
Затем сниппет updatePrice проходится по всем товарам и ищет цену в долларах, если она стоит, то пересчитывает цену в рублях по сегодняшнему курсу и подставляет уже новую цену в товар, в поле price.
Вот такой функционал у меня получился, я уверен, что есть масса вариантов как это сделать и мой один из них.
Так как я новичок в сфере программирования и хоть как-то пытаюсь принести пользу, Я надеюсь, что найдутся люди, которым моя статья поможет, всем спасибо за внимание :)
Поблагодарить автора
Отправить деньги
Комментарии: 27
сниппет getCurrency получает из ЦБ курс на сегодняшний день и помещает его в системную настройку currency.Зачем два сниппета? Не легче ли все сделать в одном чтобы не парится с временем запуска?
Затем сниппет updatePrice
CronManagerЭто не полноценный крон и если на сайт никто не заходит — событие никогда не будет выполненно, странное решение. Непонятно почему нельзя было использовать нормальный крон
$value->getTVValue('usdprice')В сниппете используется 2 раза, 1 лишний запрос в базу, непонятно почему нельзя было вынести в переменную и уже ее использовать, например
if ($us = $value->getTVValue('usdprice'))
Также не вижу округления, а если результат умножения будет с 10ю тысячными?В сниппете получения валюты получение происходит по ID, это не кошерно, не легче ли более традиционно проверять CharCode? Да и по CharCode будет быстрее, т.к. не нужно будет использовать функцию, достаточно
$val->CharCode
А так в целом пойдет, новичкам думаю пригодится т.к. все максимально просто
Почему это не полноценный крон? вполне полноценный. В настройке crontab сервера прописывает путь до исполняемого скрипта, который вызывается системой раз в минуту, а cronmanager переопределяет уже время. Юзал много раз, работает автономно (без привязки к хитам). За это отвечаю, вот инструкция docs.modx.com/extras/revo/cronmanager
Разделение на два сниппета тут логичное в плане «не клади все яйца в одну корзину». А если учесть что скорость тут не критична, ибо это делается в фоне а не в момент рендера страницы, то лишний вызов второго сниппета роли не играет
со остальными замечаниями согласен
Разделение на два сниппета тут логичное в плане «не клади все яйца в одну корзину». А если учесть что скорость тут не критична, ибо это делается в фоне а не в момент рендера страницы, то лишний вызов второго сниппета роли не играет
со остальными замечаниями согласен
В настройке crontab сервера прописывает путь до исполняемого скрипта, который вызывается системой раз в минутуУх ты, честно говоря не знал что php так может, изучу исходники детально, спасибо, думал работает по типу «На modx init дергает обработчик, который проверяет чему уже надо исполнится и исполняет»
Разделение на два сниппета тут логичное в плане «не клади все яйца в одну корзину». А если учесть что скорость тут не критична, ибо это делается в фоне а не в момент рендера страницы, то лишний вызов второго сниппета роли не играетНу тут наверное вкусовщина больше, было бы там кода строк на 500 я бы скорее всего согласился, а разбивать 40 строк по 20 так себе, ИМХО
Ух ты, честно говоря не знал что php так можетА при чём тут PHP? Скрипт запускается раз в минуту, остальное делается на уровне кода. Проверяются задания, которые соответствуют текущему времени и происходит запуск тех, которые ждут. Это вроде аналог компонента Марка — Scheduler. Как-то писал взаимодействие xParser с ним. Там для скрипта важно учитывать особенности Scheduler. Например, он сам следующего задания в планировщик не создаст, это надо делать на уровне скрипта. Не знаю, как у CronManager с этим…
А при чём тут PHP?А кто в кронтаб прописывает скрипт? Или там обычный exec который любой админ в здравом уме выключает?
В кронтаб вручную сам прописываешь запуск раз в минуту. Остальное на уровне скрипта.
Спасибо, полезно.
Я рад :)
Как оказывается разительно отличаются курсы у ЦБ и те, что показывают на главной Яндекса )) не знал…
Есть маленький вопрос, что-то не так с математикой
в системной настройке появился курс доллара 66,74 (тот же, что и у ЦБ)
Сниппет, который умножает цену у меня выглядит так (убрал TV)
Есть маленький вопрос, что-то не так с математикой
в системной настройке появился курс доллара 66,74 (тот же, что и у ЦБ)
Сниппет, который умножает цену у меня выглядит так (убрал TV)
<?php
// Получаем курс из системной опции
$currency = $modx->getOption('currency');
// Получаем все товары и проставляем цену в рублях
$col = $modx->getCollection('msProduct');
foreach ($col as $value){
$price = $value->get('price');
$sum = $currency * $price;
$set = $value->set('old_price', $sum);
$value->save();
}
После умножения товара ценой 200$ на курс почему-то получилось 13 200р, хотя калькулятор утверждает, что будет 13348. Может есть какие-то мысли отчего так происходит?
Это из-за плавающей точки. Сниппетом должен обрабатываться курс с плавающей точкой например 66.5, а приходит с запятой 66,5, я исправил, смотрите сниппет updatePrice.
Спасибо работает.
Подскажете, как записывать в результат только целое число (убрать копейки)?
Подскажете, как записывать в результат только целое число (убрать копейки)?
Да уже сделал так, хотя round не совсем то
$sum = round($currency * $price);
фунцкией floor() попробуйте, она округляет в меньшую сторону
Очень рекомендую в данный скрипт добавлять несколько попыток получения курса валют. Исхожу из личного опыта.
Очень часто сайт ЦБ отклоняет запросы сторонних серверов на получение курса. Получить курс иной раз удается только после пятой попытки в течении часа.
Очень часто сайт ЦБ отклоняет запросы сторонних серверов на получение курса. Получить курс иной раз удается только после пятой попытки в течении часа.
Получить пытаетесь отсюда www.cbr.ru/scripts/XML_daily.asp?
Да отсюда. У меня в скрипте настроено так, если не поучил курс, пытаться получить его каждые пять минут в течении часа. Лет 5 пользуюсь, и все нормально. В большинстве случаев получаю курс с первой попытки, но бывает и со второй и с пятой. Полный рандом.
странно, у нас за 4 года «ни единого разрыва»
Может от времени зависит, я в ноль часов пять минут забираю курс)
М.б. я в 20:15
У ЦБ есть определенный лимит подключений в определенный промежуток времени, не думаю, что стоит делать больше одного запроса в ЦБ. Я это уже проверял.
Нужно делать больше одной попытки или будите сидеть со старым курсом неделями)) Я делюсь своим многолетнем опытом.
Спасибо, но уже три месяца это работает и ни разу не было проблем. Как возникнут, сразу вспомню ваши слова :)
А Вы в какое время обновляете курс? по Москве в 00:00?
в 8 утра
Я предпочитаю не обновлять кроном цену товаров, а пересчитываю ее в плагине на событие msOnGetProductPrice. В price цена в долларах, а показываются пересчитанные в рублях. Курсы получаю с помощью CurrencyRate. Мне кажется что так надежнее :). Крон не повиснет при пересчете и если много товаров, то нагрузка на сервер меньше (цена персчитывается каждый раз при показе товара, но не надо пересчитывать ее для всех товатов. Только те что посмотрели).
Не знаю как способ лучше. Кому какой способ больше нравиться? Пересчитывать при показе товара или пересчитывать кроном ночью все товары? И почему?
Не знаю как способ лучше. Кому какой способ больше нравиться? Пересчитывать при показе товара или пересчитывать кроном ночью все товары? И почему?
Пересчитывать при показе — это тратить лишние ресурсы сервера, возможно когда сайт посещают 100 посетителей в сутки — это незаметно, но при больших нагрузках вы ощутите огромную разницу. Предположим на странице 20 товаров, пользователь открывает страницу — получаем 20 лишних операций. А учитывая еще и то, что плагины в принципе не самая быстрая штука в modx, получаем значительные потери в скорости рендеринга страницы
Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.