Динамическое обновление цены товара miniShop2 по курсу доллара, через Cron.

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

Подготовка


Создаем системную опцию 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.

Вот такой функционал у меня получился, я уверен, что есть масса вариантов как это сделать и мой один из них.
Так как я новичок в сфере программирования и хоть как-то пытаюсь принести пользу, Я надеюсь, что найдутся люди, которым моя статья поможет, всем спасибо за внимание :)
Vlad Brise
17 декабря 2018, 23:01
19
712
+10
Поблагодарить автора Отправить деньги

Комментарии: 27

Pavel Zarubin
18 декабря 2018, 00:49
+1
сниппет getCurrency получает из ЦБ курс на сегодняшний день и помещает его в системную настройку currency.
Затем сниппет updatePrice
Зачем два сниппета? Не легче ли все сделать в одном чтобы не парится с временем запуска?

CronManager
Это не полноценный крон и если на сайт никто не заходит — событие никогда не будет выполненно, странное решение. Непонятно почему нельзя было использовать нормальный крон

$value->getTVValue('usdprice')
В сниппете используется 2 раза, 1 лишний запрос в базу, непонятно почему нельзя было вынести в переменную и уже ее использовать, например
if ($us = $value->getTVValue('usdprice'))
Также не вижу округления, а если результат умножения будет с 10ю тысячными?

В сниппете получения валюты получение происходит по ID, это не кошерно, не легче ли более традиционно проверять CharCode? Да и по CharCode будет быстрее, т.к. не нужно будет использовать функцию, достаточно
$val->CharCode
А так в целом пойдет, новичкам думаю пригодится т.к. все максимально просто
    Сергей
    18 декабря 2018, 14:34
    +2
    Почему это не полноценный крон? вполне полноценный. В настройке crontab сервера прописывает путь до исполняемого скрипта, который вызывается системой раз в минуту, а cronmanager переопределяет уже время. Юзал много раз, работает автономно (без привязки к хитам). За это отвечаю, вот инструкция docs.modx.com/extras/revo/cronmanager

    Разделение на два сниппета тут логичное в плане «не клади все яйца в одну корзину». А если учесть что скорость тут не критична, ибо это делается в фоне а не в момент рендера страницы, то лишний вызов второго сниппета роли не играет

    со остальными замечаниями согласен
      Pavel Zarubin
      18 декабря 2018, 23:37
      0
      В настройке crontab сервера прописывает путь до исполняемого скрипта, который вызывается системой раз в минуту
      Ух ты, честно говоря не знал что php так может, изучу исходники детально, спасибо, думал работает по типу «На modx init дергает обработчик, который проверяет чему уже надо исполнится и исполняет»

      Разделение на два сниппета тут логичное в плане «не клади все яйца в одну корзину». А если учесть что скорость тут не критична, ибо это делается в фоне а не в момент рендера страницы, то лишний вызов второго сниппета роли не играет
      Ну тут наверное вкусовщина больше, было бы там кода строк на 500 я бы скорее всего согласился, а разбивать 40 строк по 20 так себе, ИМХО
        Павел Гвоздь
        19 декабря 2018, 06:38
        0
        Ух ты, честно говоря не знал что php так может
        А при чём тут PHP? Скрипт запускается раз в минуту, остальное делается на уровне кода. Проверяются задания, которые соответствуют текущему времени и происходит запуск тех, которые ждут. Это вроде аналог компонента Марка — Scheduler. Как-то писал взаимодействие xParser с ним. Там для скрипта важно учитывать особенности Scheduler. Например, он сам следующего задания в планировщик не создаст, это надо делать на уровне скрипта. Не знаю, как у CronManager с этим…
          Pavel Zarubin
          19 декабря 2018, 12:06
          0
          А при чём тут PHP?
          А кто в кронтаб прописывает скрипт? Или там обычный exec который любой админ в здравом уме выключает?
            Павел Гвоздь
            19 декабря 2018, 14:29
            0
            В кронтаб вручную сам прописываешь запуск раз в минуту. Остальное на уровне скрипта.
Алексей
19 декабря 2018, 11:30
+1
Спасибо, полезно.
    Vlad Brise
    19 декабря 2018, 13:23
    +2
    Я рад :)
Алексей
19 декабря 2018, 12:16
0
Как оказывается разительно отличаются курсы у ЦБ и те, что показывают на главной Яндекса )) не знал…

Есть маленький вопрос, что-то не так с математикой
в системной настройке появился курс доллара 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. Может есть какие-то мысли отчего так происходит?
    Vlad Brise
    19 декабря 2018, 13:23
    +3
    Это из-за плавающей точки. Сниппетом должен обрабатываться курс с плавающей точкой например 66.5, а приходит с запятой 66,5, я исправил, смотрите сниппет updatePrice.
Ivan
19 декабря 2018, 13:49
+1
Очень рекомендую в данный скрипт добавлять несколько попыток получения курса валют. Исхожу из личного опыта.
Очень часто сайт ЦБ отклоняет запросы сторонних серверов на получение курса. Получить курс иной раз удается только после пятой попытки в течении часа.
    Павел Голубев
    19 декабря 2018, 14:23
    0
    Получить пытаетесь отсюда www.cbr.ru/scripts/XML_daily.asp?
      Ivan
      19 декабря 2018, 14:52
      0
      Да отсюда. У меня в скрипте настроено так, если не поучил курс, пытаться получить его каждые пять минут в течении часа. Лет 5 пользуюсь, и все нормально. В большинстве случаев получаю курс с первой попытки, но бывает и со второй и с пятой. Полный рандом.
        Павел Голубев
        19 декабря 2018, 14:54
        0
        странно, у нас за 4 года «ни единого разрыва»
          Ivan
          19 декабря 2018, 14:56
          0
          Может от времени зависит, я в ноль часов пять минут забираю курс)
    Vlad Brise
    19 декабря 2018, 14:27
    +2
    У ЦБ есть определенный лимит подключений в определенный промежуток времени, не думаю, что стоит делать больше одного запроса в ЦБ. Я это уже проверял.
      Ivan
      19 декабря 2018, 14:55
      0
      Нужно делать больше одной попытки или будите сидеть со старым курсом неделями)) Я делюсь своим многолетнем опытом.
        Vlad Brise
        19 декабря 2018, 15:03
        +2
        Спасибо, но уже три месяца это работает и ни разу не было проблем. Как возникнут, сразу вспомню ваши слова :)
          Алексей
          19 декабря 2018, 18:30
          0
          А Вы в какое время обновляете курс? по Москве в 00:00?
            Vlad Brise
            19 декабря 2018, 19:56
            +2
            в 8 утра
Александр
22 декабря 2018, 16:52
0
Я предпочитаю не обновлять кроном цену товаров, а пересчитываю ее в плагине на событие msOnGetProductPrice. В price цена в долларах, а показываются пересчитанные в рублях. Курсы получаю с помощью CurrencyRate. Мне кажется что так надежнее :). Крон не повиснет при пересчете и если много товаров, то нагрузка на сервер меньше (цена персчитывается каждый раз при показе товара, но не надо пересчитывать ее для всех товатов. Только те что посмотрели).
Не знаю как способ лучше. Кому какой способ больше нравиться? Пересчитывать при показе товара или пересчитывать кроном ночью все товары? И почему?
    Pavel Zarubin
    24 декабря 2018, 22:46
    0
    Пересчитывать при показе — это тратить лишние ресурсы сервера, возможно когда сайт посещают 100 посетителей в сутки — это незаметно, но при больших нагрузках вы ощутите огромную разницу. Предположим на странице 20 товаров, пользователь открывает страницу — получаем 20 лишних операций. А учитывая еще и то, что плагины в принципе не самая быстрая штука в modx, получаем значительные потери в скорости рендеринга страницы
Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.