We launched new forums in March 2019—join us there. In a hurry for help with your website? Get Help Now!
    • 43084
    • 34 Posts
    Hello,

    I am using MODX rev. 2.2.7 and had the need to extend the modUser class to add a few extra fields and relationships.

    I followed the tutorial found here:

    http://rtfm.modx.com/display/revolution20/Extending+modUser

    I created the schema and generated a new model map. I also edited the extuser.class.php file according to the tutorial. Finally, I created the key extension_packages in System Settings and added the path and name for my package.

    I am using the Login 1.8.1 package to create new users from the front end and I thought that just by following these steps in the tutorial it would be enough for new users to be created with the new extUser class in class_key, but this is not the case since they are all still being created as modUser.

    Am I wrong in assuming this or am I missing some step in the procedure?

    Thanks in advance for your time.

    This question has been answered by multiple community members. See the first response.

    [ed. note: pcamelo last edited this post 13 years, 4 months ago.]
    • discuss.answer
      • 32699 ☆ A M B ☆
      • 427 Posts
      I wrote that article years ago and frankly have never seen a need for it since.

      I created dozens of applications which function as a service using the identity of current modUser and their $_SESSION, thereby keeping the two class systems completely separate, but unified.

      By creating service classes, you can easily access the other classes from withing the constructor of a class:
      		/**
      		 * Connection to TopSecret
      		 */
      		$this->topsecret = $this->modx->topSecret;
      
      		/**
      		 * Connection to Tracker
      		 */
      		$this->tracker = $this->modx->Tracker;
      


      The benefit? You don't have to worry about disappearing class keys.

      In your xml scheme you can still have cross class relationships:

      <object class="someObject" table="carts" extends="xPDOSimpleObject">
      		<field key="userid" dbtype="integer" precision="10" phptype="integer" attributes="unsigned" null="false" default="0" />
      		<aggregate alias="User" class="modUser" local="userid" foreign="id" cardinality="one" owner="foreign" />		
      		<index name="userid" primary="false" unique="false" type="BTREE">
      			<column key="userid" length="" collation="A" null="false" />
      		</index>		
      	</object>
      


      Once the service is running I simply create AJAX and other Snippets like so:

      <?php
      $secret = $modx->TopSecret;
      if (is_object($secret) && $secret instanceof TopSecret) {
      
      	///  DO STUFF HERE YOU HAVE ACCESS TO THE CURRENT USER
      }
      



      I have found this much simpler and more straight forward than extending the modUser class. It also allows me to have people working on and testing applications without giving them access to the users.
        Get your copy of MODX Revolution Building the Web Your Way http://www.sanitypress.com/books/modx-revolution-building-the-web-your-way.html

        Check out my MODX || xPDO resources here: http://www.shawnwilkerson.com
      • discuss.answer
        • 3749
        • 24,544 Posts
        The Register part of the package has code to override fields, but it's difficult to see how it works. I would try something like this in your registration form:


        <input type="hidden" name="class_key" value="yourUserClass" />



        If that doesn't work, you can create a postHook that rewrites the class_key field.
          Did I help you? Buy me a beer
          Get my Book: MODX:The Official Guide
          MODX info for everyone: http://bobsguides.com/modx.html
          My MODX Extras
          Bob's Guides is now hosted at A2 MODX Hosting
          • 43084
          • 34 Posts
          Quote from: BobRay at Jun 06, 2013, 10:33 PM
          The Register part of the package has code to override fields, but it's difficult to see how it works. I would try something like this in your registration form:


          <input name="class_key" value="yourUserClass" type="hidden">



          If that doesn't work, you can create a postHook that rewrites the class_key field.

          Thanks a lot, BobRay! That did the trick. (I feel a little bit silly now for not trying that earlier.) But now I'm stumped by something else a little bit ahead.

          And Shawn, thanks a lot for the explanation! That sounds a lot simpler indeed, but now that I have invested so much time in this I would really like to see the extended user class scheme working. The thing that attracted me in the first place to the extended user solution was the possibility to use the addOne and getOne functions easily in my snippets and also being able to save the user with my extra classes in one single step.

          Among other things, I have a script takes a couple of fields from a POST form so that when a user rates a certain item, the id of the item and its rate are saved in the new USERSCORE class for that user. Now that the class_key is properly set, my script runs without showing errors, but my extra items for the user are not being saved in the MySQL tables I created.

          This is the code for my snippet:

          if (!empty($_POST)) {
                if (isset($_POST["itemid"]) && isset($_POST["rating"])) {
                     if(!$modx->addPackage('rate','/<root path>/public_html/core/components/rate/model/','rate_')) {
                        return 'Problem loading module.';
                     }
                     $userid = $modx->user->get('id');
                     $user = $modx->getObject('extUser', $userid);
                     $myScore = $user->getOne('USERSCORE',array('Item_ID' => $_POST["itemid"]));
                     if($myScore) {
                        $myScore->set('Score',$_POST["rating"]); // this is in case the score already exists
                     }
                     else {
                        $myScore = $modx->newObject('USERSCORE');
                        $myScore->set('Item_ID',$_POST["itemid"]);
                        $myScore->set('Score',$_POST["rating"]);
                        $user->addOne($myScore);
                     }         
                     $user->save();
                     return 'Ready.';
                }
          }


          And here is a fragment of my mysql schema:

          ...
              <!-- extend the modUser class -->
              <object class="extUser" extends="modUser">
                  <composite alias="USERSCORE" class="USERSCORE" local="id" foreign="User_ID" cardinality="many" owner="local" />
              </object>
          ...
          	<object class="USERSCORE" table="USER_SCORE" extends="xPDOObject">
          		<field key="User_ID" dbtype="int" precision="10" attributes="unsigned" phptype="integer" null="false" index="pk" />
          		<field key="Item_ID" dbtype="int" precision="10" attributes="unsigned" phptype="integer" null="false" index="pk" />
          		<field key="Score" dbtype="tinyint" precision="1" phptype="integer" null="false" />
          
          		<index alias="PRIMARY" name="PRIMARY" primary="true" unique="true" type="BTREE" >
          			<column key="User_ID" length="" collation="A" null="false" />
          			<column key="Item_ID" length="" collation="A" null="false" />
          		</index>
          		<index alias="Item_ID" name="Item_ID" primary="false" unique="false" type="BTREE" >
          			<column key="Item_ID" length="" collation="A" null="false" />
          		</index>
          		<aggregate alias="extUser" class="extUser" local="User_ID" foreign="id" cardinality="one" owner="foreign" />     
          	</object>
          


          As I said, I checked directly my rate_USER_SCORE table but nothing is saved on it. Am I doing this right? Maybe I should change my condition from 'if($myScore)' to something else? Or the way the extUser class is created does not allow for this kind of manipulation?

          Thanks in advance!
            • 32699 ☆ A M B ☆
            • 427 Posts
            It's late and I won't be able to get back for a few days. You may need to change the schema to this:

            <object class="USERSCORE" table="carts" extends="xPDOSimpleObject">
            


            Somewhere in the webs in my tired mind something is screaming your call may be a table held in memory and not in the database. By I offer full disclaimer to being incorrect.


            As to investing so much time in extending the user... You can fix it in 30 minutes. And do something like:

            $obj = $modx->getObject('USERSCORE', array ('userId'=> $modx->user->getPrimaryKey());
            


            or

            $collection = $modx->getCollection('USERSCORE', array ('userId'=> $modx->user->getPrimaryKey());
            



            Take a look at this page: http://www.shawnwilkerson.com/modx-revolution/2012/03/10/programmatically-working-with-the-moduser-object/

            Pretty much everything I do there, you should be able to do, if applicable.

            Also, there is a section in my book which discusses the creation of an object test bed to play around with the object being created.

            Sorry, I have to hit the bed. Long few days ahead.
              Get your copy of MODX Revolution Building the Web Your Way http://www.sanitypress.com/books/modx-revolution-building-the-web-your-way.html

              Check out my MODX || xPDO resources here: http://www.shawnwilkerson.com
              • 28042 ☆ A M B ☆
              • 24,524 Posts
              I have your book, but I'm definitely bookmarking your cheatsheet page!
                Studying MODX in the desert - http://sottwell.com
                Tips and Tricks from the MODX Forums and Slack Channels - http://modxcookbook.com
                Join the Slack Community - http://modx.org
                • 3749
                • 24,544 Posts
                You may need to call loadClass() in addition to addPackage(). In theory, addPackage() is enough if everything is set correctly, and loadClass() will be called when you try to use one of the xPDO methods, but it can't hurt to call loadClass() after addPackage().

                More info here: http://bobsguides.com/blog.html/2013/05/25/understanding-addpackage(),-loadclass(),-and-getservice()/
                  Did I help you? Buy me a beer
                  Get my Book: MODX:The Official Guide
                  MODX info for everyone: http://bobsguides.com/modx.html
                  My MODX Extras
                  Bob's Guides is now hosted at A2 MODX Hosting
                  • 32699 ☆ A M B ☆
                  • 427 Posts
                  As you are extending a core class you may want to simply create a plugin and load your class as a service so it is always available (even from within other classes which has a reference to $modx:

                  <?php
                  /**
                   * File eWalletModxService.php
                   * Created on: April 3, 2013 2:42 PM
                   * Project shawn_wilkerson
                   * @package ewallet
                   * @version
                   * @category
                   * @author W. Shawn Wilkerson
                   * @link http://www.shawnWilkerson.com
                   * @copyright Copyright (c) 2013, Sanity LLC. All rights reserved.
                   * @license
                   *
                   * Provides site wide access to the class and underlying xPDO Model Package
                   */
                  
                  
                  $modelPath = $modx->getOption('ewallet' . '.core_path', null, $modx->getOption('core_path') . 'components/' . 'ewallet' . '/') . 'model/ewallet/';
                  $modx->getService('Ewallet', 'ewallet', $modelPath, $params= array ());
                  



                  To use it in Snippets:

                  <?php
                  $ew = $modx->Ewallet;
                  if (is_object($ew) && $ew instanceof Ewallet) {
                      return $ew->destroyCart();
                  }
                  


                  Take note of the name used as the first paramenter of getService, it is the same used to access your class via MODX: $modx->Ewallet

                  Just rename all of the ewallets to your class, assuming the path is the same, and making note of the Case changes when applicable.

                  By the way, I load the xPDO schema in the constructors of my classes:

                  try {
                  			$corePath = $this->modx->getOption('ewallet.core_path', $config, $this->modx->getOption('core_path') . 'components/ewallet/');
                  			$assetsUrl = $this->modx->getOption('ewallet.assets_url', $config, $this->modx->getOption('assets_url') . 'components/ewallet/');
                  						$this->config = array_merge(
                  					array(
                  					        'assetsUrl' => $assetsUrl, 
                  					        'cssUrl' => $assetsUrl . 'css/', 
                  					        'jsUrl' => $assetsUrl . 'js/', 
                  					        'imagesUrl' => $assetsUrl . 'images/', 
                  					        'corePath' => $corePath, 
                  					        'modelPath' => $corePath . 'model/', 
                  					        'chunksPath' => $corePath . 'elements/chunks/',
                  						'snippetsPath' => $corePath . 'elements/snippets/', 
                  					        'processorsPath' => $corePath . 'processors/', 
                  					        'prefix' => 'ew_',
                  					        
                  			         ), $config);
                  
                  			$this->modx->addPackage('ewallet', $this->config['modelPath'], $this->config['prefix']);
                  		} catch (xPDOException $xe) {
                  			$this->sendError('unavailable', array('error_message' => $xe->getMessage()));
                  		} catch (Exception $e) {
                  			$this->sendError('unavailable', array('error_message' => $e->getMessage()));
                  		}
                  



                    Get your copy of MODX Revolution Building the Web Your Way http://www.sanitypress.com/books/modx-revolution-building-the-web-your-way.html

                    Check out my MODX || xPDO resources here: http://www.shawnwilkerson.com
                    • 43084
                    • 34 Posts
                    Thanks a lot Shawn and BobRay for your replies. I have a lot of homework now!

                    Shawn, I tried to load my class as a service but I'm pretty new working with plugins or services, so I wanted to ask you a few questions since I'm not making it work.

                    Which would be the best trigger to use in this case for the plugin to load the service?

                    I tried to use OnParseDocument but I was only getting empty pages. Not sure if it was an error on my plugin or if it was the trigger I chose.

                    About a possible error in my plugin, my package has a few classes declared in it beside extUser (like USERSCORE and others), and they may or may not be used on a particular snippet depending on the user actions. Should I use one instance of getService for each class in my package? (And then test them all at the beginning of my snippet?) Or can they all be loaded in a single command? or are they loaded too when I use getService once since they are in the same package? Sorry if it sounds silly but, as I said, I'm pretty new with the concept and I'm under the impression that getService loads only the class defined as a second parameter when called and not a full package.

                    In the meantime, I'll try making my classes run with the good old addPackage until I get them to load and do what they should. I'll change extends="xPDOObject" to extends="xPDOSimpleObject" in my schema as you suggested and try explicitly loading loadClass() as BobRay suggests.

                    I'll write you later!
                    • discuss.answer
                      • 43084
                      • 34 Posts
                      Hello, everyone!

                      I tried to change my schema and declare my objects as 'extends="xPDOSimpleObject"', but after I did that, some other scripts that already worked and retrieved info from my extra tables via getObject crashed with the error:

                      PHP Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 523800 bytes) in <rootdir>/public_html/core/xpdo/om/xpdoobject.class.php on line 1668, referer: http://<mywebsite>/index.php?id=19

                      So I returned my schema to its previous configuration.

                      I also tried as BobRay suggested and used loadClass() (one for each of my classes). I didn't get any extra errors, but my script still didn't save the new values to the tables.

                      At the end, I tried changing this part of the code:

                      $myScore = $user->getOne('USERSCORE',array('Item_ID' => $_POST["itemid"]));
                      if($myScore) {
                          $myScore->set('Score',$_POST["rating"]); // this is in case the score already exists
                      }
                      else {
                          $myScore = $modx->newObject('USERSCORE');
                          $myScore->set('Item_ID',$_POST["itemid"]);
                          $myScore->set('Score',$_POST["rating"]);
                          $user->addOne($myScore);
                      }        
                      $user->save();
                      return 'Ready.';
                      


                      to this:

                      $myScore = $user->getOne('USERSCORE',array('Item_ID' => $_POST["itemid"]));
                      if($myScore) {
                          $myScore->set('Score',$_POST["rating"]); // this is in case the score already exists
                      }
                      else {
                          $myScore = $modx->newObject('USERSCORE');
                          $myScore->set('User_ID',$userid);
                          $myScore->set('Item_ID',$_POST["itemid"]);
                          $myScore->set('Score',$_POST["rating"]);
                      }
                         $myScore->save();
                         return 'Ready.';
                      


                      Which worked perfectly! Even without explicitly loading the classes with loadClass().

                      It may not be as elegant as using addOne() but it gets the job done.

                      I tested this code with an already existing item for the user just to see if the first three lines shown above worked fine and the value was indeed replaced as expected. This proves that getOne() works fine with this scheme. My guess is that $user->addOne($myScore) and $user->save() are not working because $user is an Object of type extUser which is a transient class (i.e. it doesn't have a table in the database). I don't know how to make those work with my scheme so I'll avoid them for the moment and save $myScore independently. It's still asociated with the user via the User_ID field, so I don't think that will be much of a problem.

                      I still want to load my classes as services, and after reading the info on BobRay's link I understand now that I have to use one getService for each class in my package.

                      I would still like to know what would be the best trigger to launch the plugin with my services. Could it be 'OnSiteRefresh' or 'OnSiteSettingsRender'?

                      Another question after reading BobRay's notes on getService() and loadClass(): if getService() calls loadClass() internally, how do I tell getService that the class is transient (like in the case of my extUser)?

                      And thanks for the given help. It is much appreciated!