Проблема с сохранением опций товара

Доброго времени.
Вводная информация: Modx 2.4.2 + Minishop 2.2.0

Делаю личный кабинет для партнеров, через который они могут создавать свои товары и управлять ими.
Пользователи регистрируются и авторизуются через Login, доступ только к контексту web.
Вот кусок сниппета, который отвечает за создание товара на основе уже имеющегося:

/* Получаю товар, который надо копировать */
        $r = $modx->getObject('msProduct', array(
            'id' => (int)$_GET['rid']
        ));

	/* Создаю новый с такими же свойствами и сохраняю */
        $n = $modx->newObject('msProduct', array(
            'pagetitle' => $r->pagetitle,
            'introtext' => $r->introtext,
            'content' => $r->content,
            'parent' => $r->parent,
            'template' => $r->template,
            'published' => $r->published,
            'show_in_tree' => 0,
            'createdby' => $uid,
            'source' =>  $r->source
        ));
        $n->save();

        /*
        Пытаюсь создать связь между созданным товаром и оригиналом. Не работает
        $l = $modx->newObject('msProductLink', array(
            'link' => 1,
            'master' => $r->id,
            'slave' => $n->id
        ));
        $l->save();
        */

	/* Создаю таким образом */
        $mid = $r->id;
        $sid = $n->id;
        $sql = "INSERT INTO modx_ms2_product_links (link,master,slave) VALUES ('1','$mid','$sid')";
        $stmt = $modx->prepare($sql);
        $stmt->execute();

        /* Преобразую в массив, потому что не знаю, как получить доступ к значению пользовательской опции в исходном виде */
        $r = $r->toArray();

	/* Ниже двумя способами пытаюсь задать товару значение пользовательской опции.
	В первом случае в базу пишутся значения с пустым value, во втором в базу вообще ничего не пишется. */
        $o = $modx->newObject('msProductOption', array(
            'product_id' => $sid,
            'key' => 'options_phone',
            'value' => $r['options_phone']
        ));
        $o->save();

        $n_options_phone = $r['options_phone'];
        $sql = "INSERT INTO modx_ms2_product_options(product_id,key,value) VALUES('$sid','options_phone','$n_options_phone')";
        $stmt = $modx->prepare($sql);
        $stmt->execute();
Последний кусок кода у меня есть еще в другом сниппете — который отвечает за редактирование товара, только там идет обработка другой опции.
/* Получаю товар для редактирования */
            $r = $modx->getObject('msProduct', array(
                'id' => (int)$_POST['rid']
            ));

            /* Меняю полученные данные */
            $r->fromArray(array(
                'description' => trim($_POST['description']),
                'old_price' => (int)$_POST['old_price'],
                'price' => (int)$_POST['price']
            ));
            $r->save();

            /* Меняю значение опции на полученное */
            $o = $modx->newObject('msProductOption', array(
                'product_id' => $r->id,
                'key' => 'options_length',
                'value' => trim($_POST['options_length'])
            ));
            $o->save();
В этом сниппете этот код пишет в базу вроде бы правильное значение, но когда я открываю товар для редактирования в админке ModX, после значения опции добавлена запятая. Т.е. если в личном кабинете я поменял длительность на
неделя
то при редактировании товара через админку я увижу
неделя,
Не понимаю, в чем может быть проблема. Если с запято я еще смогу справиться какими-нибудь костылями, то почему при создании товара в базу пишутся значения с пустым value — не понимаю.
Александр
20 февраля 2016, 13:41
modx.pro
3
3 467
0

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

Антон Пастухов
20 февраля 2016, 18:30
0
/* Преобразую в массив, потому что не знаю, как получить доступ к значению пользовательской опции в исходном виде */
        $r = $r->toArray();

Как-то так:

$option = $r->get('options_phone');
Попробуйте переделать с учетом этого, не гоняя значения лишний раз из объекта в массив и назад. Ну и надо смотреть значение 'options_phone' на разных этапах, $modx->log и все такое.

Ну и еще из любви к исскусству:

$r = $modx->getObject('msProduct', array(
                'id' => (int)$_POST['rid']
            ));
можно заменить на

$r = $modx->getObject('msProduct', $_POST['rid']);

    Роман Садоян
    20 февраля 2016, 23:07
    0
    Ну и еще из любви к исскусству:
    Лучше уж так:
    $r = $modx->getObject('msProduct', (int)$_POST['rid']);
    А еще лучше при получении объекта оборачивать всё это дело в if и получить переменную rid (мало ли где то еще будет использоваться):
    $id = (int)$_POST['rid'];
    if(!$r = $modx->getObject('msProduct', $id )){
    return 'Не получилось получить объект msProduct с id='.$id;
    }
      Антон Пастухов
      20 февраля 2016, 23:33
      +1
      Роман, я считаю, что явное приведение типов в PHP очень редко бывает нужно, и это не тот случай. XPDOCriteria работает с int, array или object. Проверив, что в него пришел не array и не object, он в любом случае преобразует содержимое $_POST в int.

      if(!$r = $modx->getObject('msProduct', $id )){
      Эта строчка работает, но дает ложное ощущение, что $modx->getObject() возвращает false, если объект не найден, в то время как он в этом случае возвращает null. Поэтому более правильно проверять результат с помощью is_null() или is_object().
        Роман Садоян
        21 февраля 2016, 00:32
        +1
        я считаю, что явное приведение типов в PHP очень редко бывает нужно
        Эта строчка работает, но дает ложное ощущение, что $modx->getObject() возвращает false, если объект не найден, в то время как он в этом случае возвращает null.
        Почему именно в этом случае более правильнее проверять на соответствие к типу?

          Воеводский Михаил
          21 февраля 2016, 00:46
          +1
          Ради интереса только что проверил:
          <?php
          $a = null;
          $b = false;
          if ((!$a) === (!$b)) echo 'equal';
          else echo 'not equal';
          // equal
          Вывод прост:
          !$modx->getObject()
          даст тот же результат, что и
          is_null($modx->getObject())
          , но первый вариант воспринимается проще и быстрее при чтении кода.
            Антон Пастухов
            21 февраля 2016, 01:05
            0
            , но первый вариант воспринимается проще и быстрее при чтении кода.
            Это дело вкуса. Как я сказал выше, код работает. Но все же null это не false, а только приводится к нему.

            $test = $modx->getObject('modResource', 9999); // несуществующий ID
            var_dump($test); // null
            Поскольку вызывая $modx->getObject() мы рассчитываем получить _объект_, а не булево, мне видится более правильным работать с результатом как с объектом, поэтому is_object, а не просто логическое «не». Вкусовщина, конечно.
      Александр
      21 февраля 2016, 11:30
      0
      Спасибо, но это всё вода. Изменение формы записи пары второстепенных строчек никак не приблизит меня не только к решению моей проблемы, но даже к пониманию её природы.
        Антон Пастухов
        21 февраля 2016, 15:05
        0
        Вы сделали
        Ну и надо смотреть значение 'options_phone' на разных этапах, $modx->log и все такое.
        ?

        Скорее всего, ваш options_phone' окажется пуст.
          Александр
          21 февраля 2016, 15:46
          0
          Проверял, он выводит верное значение до самого последнего момента, но при
          $stmt->execute();
          или
          $o->save();
          В базу пишется пустое значение.
      Воеводский Михаил
      21 февраля 2016, 15:06
      +1
      Код написал без проверки. В теории должен работать.

      <?php
      
      /* Получаем товар, который надо копировать */
      if ($r = $modx->getObject('msProduct', (int)$_GET['rid'])) {
      
          /* Если товар создается с такими же свойствами, корректнее их брать полностью */
          $productData = $r->toArray();
          /* Сбрасываем некоторые свойства оригинального товара */
          unset($productData['publishedon'], $productData['publishedby']);
          /* Устанавливаем правильные свойства */
          $productData['createdon'] = time();
          $productData['createdby'] = $modx->user->id;
          /* Создаем и сохраняем */
          $n = $modx->newObject('msProduct', $productData);
      
          /* Создаем новую связь */
          $link = $modx->newObject('msLink', array(
              'type' => 'one_to_many',
              'name' => 'Название связи',
          ));
          
          /* Создаем новую связку */
          $linkProducts = $modx->newObject('msProductLink');
          /* Добавляем товары в связку */
          $linkProducts->addOne($r, 'Master');
          $linkProducts->addOne($n, 'Slave');
          /* Добавляем основную связь в связку */
          $linkProducts->addOne($link, 'Link');
      
          /* Создаем новую опцию */
          $option = $modx->newObject('msProductOption', array('key' => $r->get('options_phone')));
          /* Добавляем опцию к новому товару */
          $n->addOne($option, 'Options');
      
          /* Сохраняем товар. Благодаря связыванию объектов через addOne(), все необходимые ID подставляются автоматически */
          $n->save();
          
      }
        Александр
        21 февраля 2016, 15:42
        0
        Большое спасибо за развернутый ответ.
        /* Создаем новую связь */
            $link = $modx->newObject('msLink', array(
                'type' => 'one_to_many',
                'name' => 'Название связи',
            ));
        Я не совсем понимаю, именно создаем, а не получаем, к примеру так?
        /* Создаем новую связь */
            $link = $modx->newObject('msLink', 1);
        Просто сама связь у меня уже существует под идентификатором 1.
        В целом ничего не поменялось, options_phone пишется в базу с пустым value, при этом
        $r->get('options_phone')
        выводит правильное значение.
        Связь между товарами вообще не создается — в базе не появляется записей.
          Александр
          21 февраля 2016, 16:10
          0
          Связь просто надо было сохранить
          $linkProducts->save();
          Спасибо еще раз, Михаил, за работающее решение, очень круто.
            Воеводский Михаил
            21 февраля 2016, 18:56
            +1
            Связь просто надо было сохранить
            $linkProducts->save();
            Не обратил внимание, что у товара нет прямой связи (на уровне xPDO) со связью msLink.
            Потому все равно достаточно одного сохранения, но с другой стороны — сохранить $linkProducts, а все остальные объекты тоже сохранятся. То есть,
            $n->save()
            совершенно необязательное действие, если есть строка выше.
            Воеводский Михаил
            21 февраля 2016, 18:51
            +1
            Я не совсем понимаю, именно создаем, а не получаем
            Я не совсем понял Вашу идею, потому в коде создал связь. Конечно, можно использовать созданную ранее.

            $link = $modx->newObject('msLink', 1);
            Бесполезная запись, тк newObject() вторым параметром принимает массив. Если передано что угодно другое, параметр игнорируется.

            Более того, даже если передать массив
            array('id' => 1);
            , то все равно новому объекту не будет установлено значение поля id == 1. Причина: метод fromArray(), который используется для установки переданных значений созданному объекту, по умолчанию не выставляет значение первичного ключа, коим является поле id.

            После создания объекта требуется либо явно задать первичный ключ через set():
            $link->set('id', 1);
            , либо указать третий параметр для fromArray():
            $link->fromArray(array('id' => 1), '', true);
            Если же требуется получить созданную ранее связь по ее id, используйте такой вариант:
            $link = $modx->getObject('msLink', $id);
              Александр
              22 февраля 2016, 01:31
              0
              Да, я описался, использовал именно getObject.
              Спасибо за столь подробные и работоспособные разъяснения.
            Александр
            21 февраля 2016, 15:56
            0
            Каюсь, Ваш код работает верно — товар копируется со всеми опциями абсолютно верно. Они затираются на следующем шаге, когда я пытаюсь поменять опцию options_length у новосозданного товара
            /* Получаю товар для редактирования */
                        $r = $modx->getObject('msProduct', array(
                            'id' => (int)$_POST['rid']
                        ));
            
                        /* Меняю полученные данные */
                        $r->fromArray(array(
                            'description' => trim($_POST['description']),
                            'old_price' => (int)$_POST['old_price'],
                            'price' => (int)$_POST['price']
                        ));
                        $r->save();
            
                        /* Меняю значение опции на полученное */
                        $o = $modx->newObject('msProductOption', array(
                            'product_id' => $r->id,
                            'key' => 'options_length',
                            'value' => trim($_POST['options_length'])
                        ));
                        $o->save();
            После этого значения опции options_phone и options_length затираются и появляется дублирующая строка с опцией options_length.
            С этим, думаю, я уже смогу разобраться. Главное что уже понятно, где происходит ошибка.
              Воеводский Михаил
              21 февраля 2016, 19:27
              1
              +1
              /* Меняю значение опции на полученное */
                          $o = $modx->newObject('msProductOption', array(
                              'product_id' => $r->id,
                              'key' => 'options_length',
                              'value' => trim($_POST['options_length'])
                          ));
                          $o->save();
              Не надо так делать. Достаточно проверить существование опции у товара, а выставлять значение опции самому товару. Как минимум, такой механизм я понял из чтения исходников MS 2.2 (с опциями мне еще не приходилось работать).

              <?php
              $optionKey = 'options_length';
              $optionKeys = $r->getOptionKeys();
              if (!in_array($optionKey, $optionKeys) and $option = $modx->getObject('msOption', array('key' => $optionKey))) {
                  $productOption = $modx->newObject('msProductOption');
                  $productOption->addOne($option, 'Option');
                  $productOption->addOne($r, 'Product');
              }
              $r->set($optionKey, trim($_POST[$optionKey]));
              $r->save();
              Александр
              21 февраля 2016, 17:47
              0
              Если не сложно, Михаил, подскажите ответ на еще 1 вопрос.
              Не понимаю, как мне поменять значение опции у создаваемого или уже существующего товара на то, то я получаю POST-запросом от пользователя. Прикладываю весь код сниппета, но вопрос исключительно по той части, где комментарий.
              <?php
              if (empty($modx->getLoginUserID('web')))
                  $modx->sendRedirect($modx->makeUrl(1));
              
              $uid = $modx->getLoginUserID('web');
              
              if (!empty($_POST)) {
                  $r = $modx->getObject('msProduct', (int)$_POST['rid']);
                  if (!$r || $uid != $r->createdby)
                      $modx->sendRedirect($modx->makeUrl(8));
              
                  $productData = $r->toArray();
                  unset($productData['publishedon']);
                  $productData['createdon'] = time();
                  $productData['published'] = 1;
                  $productData['favorite'] = 0;
                  $productData['description'] = trim($_POST['description']);
                  $productData['old_price'] = (int)$_POST['old_price'];
                  $productData['price'] = (int)$_POST['price'];
              
                  $n = $modx->newObject('msProduct', $productData);
              
                  $link = $modx->getObject('msLink', 1);
              
                  $linkProducts = $modx->newObject('msProductLink');
                  $linkProducts->addOne($r, 'Master');
                  $linkProducts->addOne($n, 'Slave');
                  $linkProducts->addOne($link, 'Link');
                  $linkProducts->save();
              
                  $optionPhone = $modx->newObject('msProductOption', array('key' => $r->get('options_phone')));
                  $n->addOne($optionPhone, 'Options');
                  $optionExp = $modx->newObject('msProductOption', array('key' => $r->get('options_experience')));
                  $n->addOne($optionExp, 'Options');
              /* С копированием опций более-менее понятно. Не понимаю как поменять значение опции на получаемое из формы */
                  $optionLength = $modx->newObject('msProductOption', array('key' => trim($_POST['options_length'])));
                  $n->addOne($optionLength, 'Options');
              
                  $n->save();
              
                  $modx->sendRedirect($modx->makeUrl(8));
              }
              
              return $output;
            Михаил
            10 января 2017, 16:44
            0
            Вопрос немного не по теме. В php не силён. ПО этому и спрашиваю :)

            Меня интересует как мне сохранить много массиво в качестве ресурсов MODX

            Объясню кратко: Спарсил кое какие данные с сайта (php Query )? распихал данные по массивам. И теперь мне нужно эти массивы сохранить в базе модекс как ресурсы.

            Пробывал вот так:
            <?php
            header('Content-type: text/html; charset-utf-8');
              require ('phpQuery-onefile.php');
              
              function get_content($url) {
                $ch = curl_init($url);
                curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
                curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
                $res = curl_exec($ch);
                return $res;
              }
              function parser ($url, $start, $end) {
                if ($start < $end) {
            
                    $file = get_content($url);
                    $doc = phpQuery::newDocument($file);
                    
                    foreach ($doc->find('.productItem') as $article ) {
                        $article = pq($article);
                        $titl = $article->find('.h3-like.title a')->text();
                       
                        $resurs = array(
                            'pagetitle' => $titl,
                            'longtitle' => $titl,
                            'description' => $titl,
                            'introtext' => $titl,
                            'content' => 'Содержимое тестовой страницы',
                            'alias' => 'new-page',
                            'template' => 1,
                        	'published' => 1,
                        	'parent' => 0
                        );
                        
                          $resource = $modx->newObject('modResource');
                          $resource->fromArray($resurs);
                          $resource->save();
                        
                        echo '<pre>';
                        print_r ($resurs);
                        echo '</pre>';
            
                    }
                    
                    /* Шагаем по страницам */
                    $next = $doc->find('.pagination li.active')->next()->find('a')->attr('href');
                    if ( !empty($next)){
                        $start++;
                        parser($url, $start, $end);
                    }
            
                }
                
            
              }
              
            
              
              $url = 'https://www.letu.ru/idei-dlya-podarka/podarki-dlya-nego';
              $start = 0;
              $end = 1;
              parser($url, $start, $end);
            Не получается…

            Подскажите пожайлуста новичку)
              Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
              21