Связать кастомную таблицу с modUsers

Связать пользовательскую таблицу с modUser. Требуются расширить данные о пользователе, поле extended в modx_user_attributes не подходит из за json, поля будут активно участвовать в поиске.
в пакете modExtra-master(MyExtUser) создаю простейшую схему:
1.
<?xml version="1.0" encoding="UTF-8"?>
<model package="myextuser" baseClass="xPDOObject" platform="mysql" defaultEngine="MyISAM" phpdoc-package="myextuser"
	   version="1.1">

    <object class="extUser" extends="modUser">
        <composite alias="Data" local="id" class="Userdata" foreign="userdata_id" cardinality="one" owner="local" />
    </object>

    <object class="Userdata" table="data" extends="xPDOSimpleObject">
        <field key="userdata_id" dbtype="int" precision="11" phptype="integer" null="false" attributes="unsigned"/>
        <field key="anyfield" dbtype="varchar" precision="100" phptype="string" null="true" />

        <aggregate alias="extUser" local="userdata_id" foreign="id" cardinality="one" owner="foreign" />
    </object>

</model>
2. Генерирую модель, синхронизирую, делаю все как тут: bezumkin.ru/training/course1/2136/

3. В резолвере прописываю объекты
$objects = array(
		/*'MyExtUserItem',*/
                'extUser',
                'Userdata'
			);
4. Прописываю системные настройки:

Ключ: myextuser_assets_url Знаачение: {assets_path}components/myextuser/
Ключ: myextuser_core_path Значение: {core_path}components/myextuser/

5. Собираю пакет, устанавливаю. Создаю сниппет
6.
if (!$MyExtUser = $modx->getService('myextuser', 'MyExtUser', $modx->getOption('myextuser_core_path', null, $modx->getOption('core_path') . 'components/myextuser/') . 'model/myextuser/', $scriptProperties)) {
	return 'Could not load MyExtUser class!';
}

$User = $modx->getObject('modUser', $modx->user->get('id'));
$Data = $User->getOne('Data');
$Data->set('anyfield','any text');
Итог: ошибка в логе

[2015-06-09 13:17:38] (ERROR @ /index.php) No foreign key definition for parentClass: modUser using relation alias: Data
Владимир Бабусенко
09 июня 2015, 10:46
modx.pro
4
3 296
+1

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

Василий Наумкин
09 июня 2015, 13:57
0
Всё верно. Ты создал новый объект и добавил ему связь с modUser.

А у modUser, как не было связи с твоим новым объектом, так и нет. Нужно где-то её прописать, например вот так, только тебе нужно заменить ключ массива Services на Data.
    Владимир Бабусенко
    09 июня 2015, 14:20
    0
    Прописал
    <?php
    
    $xpdo_meta_map = array (
      'modUser' => 
      array (
        0 => 'extUser',
      ),
      'xPDOSimpleObject' => 
      array (
        0 => 'Userdata',
      ),
    );
    
    $this->map['modUser']['composites']['Data'] = array(
        'class' => 'Userdata',
        'local' => 'id',
        'foreign' => 'userdata_id',
        'cardinality' => 'one',
        'owner' => 'local',
    );
    прописываю в файле MyExtUser/core/components/myextuser/model/myextuser/metadata.mysql.php
    Но при генерации пакета удаляется добавленная связь…
      Василий Наумкин
      09 июня 2015, 14:22
      0
      Естественно, удаляется — файл же перезаписывается.

      Придётся прописывать заново каждый раз. Других способов не знаю, извини.
      Владимир Бабусенко
      09 июня 2015, 14:46
      0
      что сразу не обратил. Вы пишите что я создал новый объект в моем случае (Userdata), и добавил связь с modUser, а именно
      <aggregate alias="extUser" local="userdata_id" foreign="id" cardinality="one" owner="foreign" />
      А у modUser не было связи и нет… меня смутило. Как же, я же расширяю объект modUser и прописываю связь. Разве не такой принцип? При генерации модели, разве это связь не должна учитываться?
      <object class="extUser" extends="modUser">
              <composite alias="Data" local="id" class="Userdata" foreign="userdata_id" cardinality="one" owner="local" />
          </object>
        Василий Наумкин
        09 июня 2015, 14:48
        0
        Разве не такой принцип?
        Нет.

        Ты работаешь со своими объектами и к чужим отношения не имеешь. Модель MODX уже сгенерирована и лежит в отдельной директории. Как ты думаешь, она должна перегенерироваться в зависимости от твоих действий?

        Верно, никак. Она о твоих действиях вообще не в курсе.
          Владимир Бабусенко
          09 июня 2015, 15:40
          0
          Вы правы, не поспоришь. Может подскажите как реализовать, как пример: отношение объектов user и user_attributes. Как я понимаю, достаточно создать одну схему(объект), указать связь с modUser.

          <?xml version="1.0" encoding="UTF-8"?>
          <model package="myextuser" baseClass="xPDOObject" platform="mysql" defaultEngine="MyISAM" phpdoc-package="myextuser"
          	   version="1.1">
          
              <object class="Userdata" table="data" extends="xPDOSimpleObject">
                  <field key="userdata_id" dbtype="int" precision="11" phptype="integer" null="false" attributes="unsigned"/>
                  <field key="anyfield" dbtype="varchar" precision="100" phptype="string" null="true" />
          
                  <aggregate alias="extUser" class="modUser" local="userdata_id" foreign="id" cardinality="one" owner="foreign" />
              </object>
          
          </model>
          и прописать
          $this->map['modUser']['composites']['Services'] = array(
          	'class' => 'Userdata',
          	'local' => 'id',
          	'foreign' => 'userdata_id',
          	'cardinality' => 'one',
          	'owner' => 'local',
          );


            Владимир Бабусенко
            09 июня 2015, 18:33
            0
            Василий, что не так в этот раз. Я немного пересмотрел связь объектов, и теперь делаю так.

            Создаю схему:
            <?xml version="1.0" encoding="UTF-8"?>
            <model package="myextuser" baseClass="xPDOObject" platform="mysql" defaultEngine="MyISAM" phpdoc-package="myextuser"
            	   version="1.1">
            
            
                <object class="Userdata" table="data" extends="xPDOSimpleObject">
                    <field key="userdata_id" dbtype="int" precision="11" phptype="integer" null="false" attributes="unsigned"/>
                    <field key="anyfield" dbtype="varchar" precision="100" phptype="string" null="true" />
            
                    <aggregate alias="extUser" class="modUser" local="userdata_id" foreign="id" cardinality="one" owner="foreign" />
                </object>
            
            </model>
            Генерирую модель как описано у вас bezumkin.ru/training/course1/2136/

            Добавил системные настройки с ключем
            extension_packages
            со значением
            [{"myextuser":{"path":"[[++core_path]]components/myextuser/model/"}}]
            Ключ: myextuser_assets_url Значение: {assets_path}components/myextuser/
            Ключ: myextuser_core_path Значение: {core_path}components/myextuser/

            Собрал и установил пакет myextuser.

            Добавил в файл /public_html/core/components/myextuser/model/myextuser/metadata.mysql.php
            <?php
            
            $xpdo_meta_map = array (
              'xPDOSimpleObject' => 
              array (
                0 => 'Userdata',
              ),
            );
            
            $this->map['modUser']['composites']['Services'] = array(
                'class' => 'Userdata',
                'local' => 'id',
                'foreign' => 'userdata_id',
                'cardinality' => 'one',
                'owner' => 'local',
            );
            Создал сниппет:
            $modx->addPackage('myextuser', MODX_CORE_PATH . 'components/myextuser/model/');
            
            if (!$MyExtUser = $modx->getService('myextuser', 'MyExtUser', $modx->getOption('myextuser_core_path', null, $modx->getOption('core_path') . 'components/myextuser/') . 'model/myextuser/', $scriptProperties)) {
            	return 'Could not load MyExtUser class!';
            }
            
            $User = $modx->getObject('modUser', $modx->user->get('id'));
            $Data = $User->getOne('Services');
            $Data->set('anyfield','any text'); 
            $Data->save();
            Выдает 500 ошибку… что я не так делаю?
        Воеводский Михаил
        09 июня 2015, 14:27
        0
        <object class="chGuest" extends="modUser">
                <field key="class_key" dbtype="varchar" precision="45" phptype="string" null="false" default="chGuest"/>
        
                <composite alias="Data" class="chGuestData" local="id" foreign="id" cardinality="one" owner="local" />
        </object>
        
        <object class="chGuestData" table="ch_guests_data" extends="xPDOSimpleObject">
                <field key="surname" dbtype="varchar" precision="45" phptype="string" null="false" default=""/>
                <field key="name" dbtype="varchar" precision="45" phptype="string" null="false" default=""/>
                <field key="patronymic" dbtype="varchar" precision="45" phptype="string" null="false" default=""/>
        
                <composite alias="Guest" class="chGuest" local="id" foreign="id" cardinality="one" owner="foreign" />
        </object>

        Иной вариант — расширение пользователей с указанием нового класса.
        Связь Data — по аналогии с msProduct, очень сильно упрощает работу с полями в дополнительной таблице.
        Класс chGuestData — стандартный класс, расширяющий xPDOSimpleObject.
        chGuest содержит методы прозрачного подтягивания полей из chGuestData: github.com/bezumkin/miniShop2/blob/master/core/components/minishop2/model/minishop2/msproduct.class.php
          Василий Наумкин
          09 июня 2015, 14:50
          +1
          Ты забыл указать, что всем юзерам сайта придётся указывать class_key = chGuest, что может быть не очень удобно по разным причинам.
            Воеводский Михаил
            09 июня 2015, 14:57
            0
            Верно, спасибо за замечание.

            Необходимо смотреть, что удобнее для конкретной задачи.
          Сергей Шлоков
          09 июня 2015, 15:51
          0
          Еще есть вот такой вариант расширения классов.
            Алексей Карташов
            09 июня 2015, 18:52
            0
            Вот мой резолвер из одного проекта:
            gist.github.com/antixrist/be29c8381214654af04e

            Вам нужны вот эти строчки:
            gist.github.com/antixrist/be29c8381214654af04e#file-resolve-tables-php-L38-L57

            Подставьте вместо вот этого вашу связь:
            $this->map['modUser']['composites']['Data'] = array(
                'class' => 'Userdata',
                'local' => 'id',
                'foreign' => 'userdata_id',
                'cardinality' => 'one',
                'owner' => 'local',
            );

            Ну и при копипасте не забудьте подставить свои пути/настройки.

            Смысл в том, чтобы автоматически добавлять нужную связь к системному классу при пере-/установке своего компонента.

            Если что-то не понятно — спрашивайте)
              Владимир Бабусенко
              09 июня 2015, 19:04
              0
              Алексей спасибо, ценная вещь, обязательно воспользуюсь, т.к у меня все пакетами идет. Но мне пока не запустить связанный объект, может вы подскажите. Выше я спросил Василия, но не соображу как дать ссылку на описание моих действий, поэтому, извините, продублирую......, по сравнению с 1 постом схема изменилась в резольвере, соответсвенно прописан только один объект
              $objects = array(
              				/*'MyExtUserItem',*/
                              'Userdata'
              			);
              Создаю схему:
              <?xml version="1.0" encoding="UTF-8"?>
              <model package="myextuser" baseClass="xPDOObject" platform="mysql" defaultEngine="MyISAM" phpdoc-package="myextuser"
              	   version="1.1">
              
              
                  <object class="Userdata" table="data" extends="xPDOSimpleObject">
                      <field key="userdata_id" dbtype="int" precision="11" phptype="integer" null="false" attributes="unsigned"/>
                      <field key="anyfield" dbtype="varchar" precision="100" phptype="string" null="true" />
              
                      <aggregate alias="extUser" class="modUser" local="userdata_id" foreign="id" cardinality="one" owner="foreign" />
                  </object>
              
              </model>
              Генерирую модель как описано у вас bezumkin.ru/training/course1/2136/

              Добавил системные настройки с ключем
              extension_packages со значением
              [{«myextuser»:{«path»:"[[++core_path]]components/myextuser/model/"}}]Ключ: myextuser_assets_url Значение: {assets_path}components/myextuser/
              Ключ: myextuser_core_path Значение: {core_path}components/myextuser/

              Собрал и установил пакет myextuser.

              Добавил в файл /public_html/core/components/myextuser/model/myextuser/metadata.mysql.php
              <?php
              
              $xpdo_meta_map = array (
                'xPDOSimpleObject' => 
                array (
                  0 => 'Userdata',
                ),
              );
              
              $this->map['modUser']['composites']['Services'] = array(
                  'class' => 'Userdata',
                  'local' => 'id',
                  'foreign' => 'userdata_id',
                  'cardinality' => 'one',
                  'owner' => 'local',
              );
              Создал сниппет:
              $modx->addPackage('myextuser', MODX_CORE_PATH . 'components/myextuser/model/');
              
              if (!$MyExtUser = $modx->getService('myextuser', 'MyExtUser', $modx->getOption('myextuser_core_path', null, $modx->getOption('core_path') . 'components/myextuser/') . 'model/myextuser/', $scriptProperties)) {
              	return 'Could not load MyExtUser class!';
              }
              
              $User = $modx->getObject('modUser', $modx->user->get('id'));
              $Data = $User->getOne('Services');
              $Data->set('anyfield','any text'); 
              $Data->save();
              500 ошибка
                Алексей Карташов
                09 июня 2015, 19:07
                0
                Добавьте в начало вашего сниппета вот это:
                error_reporting(E_ALL);
                ini_set('display_errors', 1);

                Дальше смотрите ошибку. А пока не охота медитировать над кодо-текстом)

                p.s. не забудьте на рабочем сайте удалить эти строки. Это только для отладки
                  Владимир Бабусенко
                  09 июня 2015, 19:33
                  0
                  Выдает Fatal error: Call to a member function set() on a non-object in public_html/core/cache/includes/elements/modsnippet/45.include.cache.php on line 14
                  Не нравится ему метод set. Весь вопрос почему?
                    Алексей Карташов
                    09 июня 2015, 19:40
                    +1
                    Ему не «set» не нравится. Ему не нравится, что в переменной $Data ничего нет.
                    Т.е. вам обязательно в коде нужно делать проверку типа такой:
                    if ($Data instanceof MyExtUser) {
                      $Data->set('anyfield','any text'); 
                      $Data->save();
                    }

                    Другой вопрос — почему в этой переменной ничего нет? Есть ли в вашей таблице получаемый объект? Рискну предположить, что нет)

                    Сперва создайте объект (либо через $User->addOne('MyExtUser', array(/*...*/)), либо $modx->newObject('MyExtUser')), а уж потом получайте его.
                      Владимир Бабусенко
                      09 июня 2015, 20:09
                      0
                      Алексей, все таки если помочь захотите, то кодо-текст посмотреть надо будет. Он тем более создан самый простой, для проверки работы, смысловой нагрузки никакой не несет. Могу на словах описать что я хотел получить. Я хотел расширить атрибуты юзера, через свою таблицу Userdata. Что бы работа осуществлялась как встроенные объекты user и user_attributes.
                      Т.е. создать связь user и моя таблица. И записывать туда специфичные данные юзера. Т.е. с профайлом modx работает так (но меня таблица не устраивает):

                      $user = $modx->getObject('modUser', $modx->user->get('id'));
                      $profile = $user->getOne('Profile');
                      $profile->set('fullname', $_POST['fullname']);
                      $profile->save();
                      Поэтому я хотел создать свою таблицу, со своими полями и работать так (создать связь с базовым класом)
                      $user = $modx->getObject('modUser', $modx->user->get('id'));
                      $profile = $user->getOne('Services'); //алиас связи с моей таблицей
                      $profile->set('fullname', $_POST['fullname']);
                      $profile->save();
                        Алексей Карташов
                        09 июня 2015, 20:20
                        0
                        кодо-текст посмотреть надо будет
                        Могу на словах описать что я хотел получить
                        Мысль про кодо-текст была о том, что вылавливать ошибку, читая код в виде текста, желания не возникает. Но смысловая нагрузка вашего кода понятна с первого взгляда.

                        А вот вы, похоже, не совсем понимаете как работают табличные объекты и связи между ними.

                        Изучайте документацию:
                        rtfm.modx.com/xpdo/2.x/getting-started/using-your-xpdo-model/creating-objects
                        rtfm.modx.com/xpdo/2.x/getting-started/using-your-xpdo-model/setting-object-fields
                        rtfm.modx.com/xpdo/2.x/getting-started/using-your-xpdo-model/working-with-related-objects
                          Владимир Бабусенко
                          09 июня 2015, 20:40
                          0
                          А вот вы, похоже, не совсем понимаете как работают табличные объекты и связи между ними.
                          Я конечно не гуру modx, но он меня не перестает удивлять.

                          $User = $modx->getObject('modUser', $modx->user->get('id'));
                          $Data = $modx->newObject('Userdata');
                          $Data->set('anyfield','any text'); 
                          $User->addOne($Data);
                          $User->save();
                          Так работает, я честно говоря думал, что объект уже создан… вернее создается экземпляр класса при обращении по алиасу т.е. в этой строке
                          $profile = $user->getOne('Services');
                          где то глубоко в недрах modx
                            Алексей Карташов
                            09 июня 2015, 21:50
                            0
                            думал, что объект уже создан… вернее создается экземпляр класса при обращении по алиасу
                            С чего это вдруг?) Если xpdo будет на каждое обращение к новым объектам создавать все его зависимые объекты, то был бы просто ппц.
                            Поэтому, нужен объект — извольте его сперва создать)
                              Владимир Бабусенко
                              09 июня 2015, 22:24
                              0
                              Алексей, вы правы. Спасибо огромное вам и всем остальным за помощь. Я еще не пробовал ваш первый пост, с резольвером, если что не так будет получатся отпишу…
                                Алексей Карташов
                                09 июня 2015, 22:25
                                0
                                Не за что)
                                Владимир Бабусенко
                                09 июня 2015, 23:00
                                0
                                Все отлично работает. Связь добавляется.

                                вопрос еще, скорей всего его уже обсуждали. Глядя на резольвер, на первые две строчки,
                                $modelPath = $modx->getOption('myextuser_core_path', null, $modx->getOption('core_path') . 'components/myextuser/') . 'model/';
                                			$modx->addPackage('myextuser', $modelPath);
                                логичней заменить?
                                $modx->addPackage
                                на
                                $modx->addExtensionPackage
                                ?
                                Алексей Карташов
                                09 июня 2015, 23:51
                                0
                                Не. Деталей не помню, но там были какие-то приколы и ошибки нелогичные при упаковывании компонента.
                                Поэтому сперва addPackage, создаём/удаляем/обновляем таблицы, потом removeExtensionPackage, а потом addExtensionPackage.
                                Вот только в этом порядке при сборке пакета исчезли эти странные ошибки.
                                С тех пор эта копипаста у меня так и гуляет из компонента в компонент)
                                Владимир Бабусенко
                                10 июня 2015, 18:13
                                0
                                Дописал после $modx->addPackage('myextuser', $modelPath);
                                $modx->addExtensionPackage, срабатывает при второй сборке пакета. В общем пока так пойдет.

                                Возник вопрос. Есть какой нибудь инструмент переводящий структуру таблиц на mysql в xml schema xpdo modx.......?
                                Илья Уткин
                                10 июня 2015, 18:22
                                0
                                CMPGenerator или даже UiCMPGenerator

                                Владимир Бабусенко
                                10 июня 2015, 18:25
                                0
                                CMPGenerator или даже UiCMPGenerator
                                Спасибо…
            Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
            27