We launched new forums in March 2019—join us there. In a hurry for help with your website? Get Help Now!
    • 37031
    • 93 Posts
    As I'm learning how to develop in MODX, two resources that have been invaluable to me have been Bob's Custom Database Tables tutorial (http://bobsguides.com/custom-db-tables.html) and Shaun's Developing an Extra in MODX Revolution (http://rtfm.modx.com/display/revolution20/Developing+an+Extra+in+MODX+Revolution). Both are great but I've found myself wondering about the differences between the two. My biggest questions are:

    1. How come the class files in Shaun's tutorial don't need to extend the xPDOSimpleObject class as in Bob's tutorial?
    2. Is it possible to incorporate some of the techniques from Shaun's Doodles class such as the modX reference, the config array, and the getChunk method, into the automatically generated classes in Bob's tutorial?
    3. Why does Shaun's tutorial use getService to load classes while Bob's tutorial uses addPackage? Is there an advantage to either?
    Any insight would be greatly appreciated. Thanks everyone!
      • 4172
      • 5,888 Posts
      How come the class files in Shaun's tutorial don't need to extend the xPDOSimpleObject class as in Bob's tutorial?
      In Shaun's tutorial Doodle extends xPDOSimpleObject, too.
      <object class="Doodle" table="doodles" extends="xPDOSimpleObject">


      If you are talking about the Doodles - class. This is not a xpoObject - class, its a 'helper' - class, where you can define app-wide methods and vars.

      Is it possible to incorporate some of the techniques from Shaun's Doodles class such as the modX reference, the config array, and the getChunk method, into the automatically generated classes in Bob's tutorial?

      they are better in the 'helper' - class.
      But, of course, you can add Object-specific - methods to the generated xpdoObject - classes

      Why does Shaun's tutorial use getService to load classes while Bob's tutorial uses addPackage? Is there an advantage to either?

      with getService he loads the 'helper' - class (Doodles)

      In the Construcotr of the Doodles - class, he is loading the package:

      Bravo! You've just made your maps and classes. Let's go make an adjustment to our Doodles base class, so it automatically adds in the Doodles xPDO package whenever we load the class. Add this line after the $this->config = array_merge part, at the end of the constructor:

      $this->modx->addPackage('doodles',$this->config['modelPath']);

      This means, every time you ara loading the Doodles-class, the doodles-package is automatically loaded and you can use the Doodle - object in all following script without adding the loadPackage - method into all your other scripts.

      You may also want to have a look into this Tutorial for easier creating packages and CMPs
      http://rtfm.modx.com/display/ADDON/MIGXdb.Create+doodles+manager+with+help+of+MIGXdb

        -------------------------------

        you can buy me a beer, if you like MIGX

        http://webcmsolutions.de/migx.html

        Thanks!
        • 42602
        • 81 Posts
        Lets see if I can clarify,

        #1 The "Doodles" base class in Shauns tutorial does not extend any xPDOObject type as it is not object in that sense, it is more of a service class (more in answer #3). The model itself has the object Doodle which does extend xPDOSimpleObject. Differences between xPDOObject and xPDOSimpleObject are small, the xPDOSimpleObject just has additionally set auto_increment field "id" automatically where with xPDOObject does not have this automation. xPDOObject is more commonly used with tables which have compound primary key. Examples of the models schemas can be found from http://rtfm.modx.com/display/xPDO20/More+Examples+of+xPDO+XML+Schema+Files. Tho quite bad as many of them are xPDOObject's with some "id" field and attribute 'generated="native"' which actually does set the auto_increment to that field automatically.

        #2 You can edit the class files that are generated with Bob's script and add the getChunk method or you can edit this part from Bob's script
        $generator->classTemplate= <<<EOD
        <?php
        /**
         * [+phpdoc-package+]
         * [+phpdoc-subpackage+]
         */
        class [[+class]] extends [[+extends]] {
        }
        ?>


        To look like this :
        $generator->classTemplate= <<<'EOD'
        <?php
        /**
         * [+phpdoc-package+]
         * [+phpdoc-subpackage+]
         */
        class [[+class]] extends [[+extends]] {
            public function getChunk($name,$properties = array()) {
                $chunk = null;
                if (!isset($this->chunks[$name])) {
                    $chunk = $this->_getTplChunk($name);
                    if (empty($chunk)) {
                        $chunk = $this->modx->getObject('modChunk',array('name' => $name));
                        if ($chunk == false) return false;
                    }
                    $this->chunks[$name] = $chunk->getContent();
                } else {
                    $o = $this->chunks[$name];
                    $chunk = $this->modx->newObject('modChunk');
                    $chunk->setContent($o);
                }
                $chunk->setCacheable(false);
                return $chunk->process($properties);
            }
             
            private function _getTplChunk($name,$postfix = '.chunk.tpl') {
                $chunk = false;
                $f = $this->config['chunksPath'].strtolower($name).$postfix;
                if (file_exists($f)) {
                    $o = file_get_contents($f);
                    $chunk = $this->modx->newObject('modChunk');
                    $chunk->set('name',$name);
                    $chunk->setContent($o);
                }
                return $chunk;
            }
        }
        EOD;
        ?>


        Notice the single quotes at the replaced 'EOD' start, this changes the Heredoc to Nowdoc so the variables do not get processed when running it. Without the single quotes the script will most likely dies as it would check variables like $name etc...

        #3 Straight link to xPDO documenation about the service : http://rtfm.modx.com/display/xPDO20/As+Service+Layer. The advantage with service class is that when you add the service to $modx. You can use it later on with $modx->yourService->someMethod. Nifty if you add it with some plugin or snippet at the start of page rendering. Also the service class can set "defaults" like addPackage() for you model. This way you don't need to add package later on at any point during page render.
          • 3749
          • 24,544 Posts
          1. xPDOSimpleObject extends xPDOObject. The only real difference is that xPDOSimpleObject has a built-in, autoincrement, primary key named 'id' so you don't have to create one. If you look closely at the Doodles tutorial, you'll see that the base class is xPDOObject, but the Doodle class itself extends xPDOSimpleObject.

          2. Once the class and map files are generated, you can do whatever you want (within reason) to the class files and add any functionality you want. You can certainly add the $this->modx reference to the constructor and getChunk(), or any other methods you want.

          3. I have never completely understood the differences between getService(), addPackage(), and loadClass() and I find the docs on them thin and misleading.


          addPackage() supposedly loads the class and sets up the paths to the directories of the package, but with this code, addPackage() is successful and loadClass() fails:

          $loginModelPath = MODX_CORE_PATH . 'components/login/model/';
          
          $success = $modx->addPackage('Login',$loginModelPath , '');
          echo "\naddPackage " . ($success? 'OK' : 'Failed');
          // OK
          
          $success = $modx->loadClass('login.Login');
          echo "\nloadClass " . ($success  ? 'OK' : 'Failed');
          // Failed
          



          The docs imply that addPackage() will let you access other classes in the package, but this code works fine without addPackage():

          $loginModelPath = MODX_CORE_PATH . 'components/login/model/';
          $success = $modx->loadClass('recaptcha.reCaptcha', $loginModelPath, true, true);
          $reCaptcha = new reCaptcha($modx);
          


          I've never been sure what addPackage() does that loadClass() doesn't. My best guess is that addPackage() also loads the class metadata, which would mean that it's only useful for classes that represent objects in the database.

          getService() loads the class file, but it also adds the class to the $modx object. So if you've added the doodles class with getService(), for example, you could use $modx->doodles->getChunk(). This will work inside another class or anywhere the $modx object is available. This is very handy for CMPs like Doodles. It returns null on failure, and as long as you include the path, you don't need to run either addPackage() or loadClass() before calling it:

          $loginModelPath = MODX_CORE_PATH . 'components/login/model/';
          $success = $modx->getService('login', 'login.Login', $loginModelPath);
          echo "\ngetService" . ($success != null ? 'OK' : 'Failed');
          // OK
          if ($modx->login instanceof Login) {
              echo "\n Login OK";
          } 
          // Login OK
          



          Both addPackage() and loadClass() return false on failure, but I've found that in some circumstances (e.g., when you fail to include the path), they can return non-false but you still can't instantiate the class.

          Again, I'm guessing somewhat, but I think this is about right:


          • If you just want to instantiate some class that doesn't represent a DB object, use loadClass().
          • If you want to instantiate some class that *does* represent a DB object and have generated class and map files for it, use addPackage().
          • If you want the class to be available as $modx->className, or if the class is already registered as a MODX service (e.g., modMail, modLexicon, modError) use getService(). In the latter case, you don't need a path.

          Hopefully, someone more enlightened will chime in here and clarify this.
            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
            • 42602
            • 81 Posts
            For loadClass() have to correct a bit, the loadClass method is seldom used directly but can be used. The loadClass method loads the database specific class. For example if Doodles would have support for sqlsrv also; the loadClass() method would load "/model/sqlsrv/doodle.class.php" class instead of "/model/mysql/doodle.class.php" if the underlying platform uses sqlsrv as database platform.

            Reason why it is rarely used by itself outside modx classes is that newObject(), getObject(), getObjectCollection(), call() etc... use it with many other methods at the background. But for example it can be used efficiently to load classes within extensions without problem, just to remember to define $path variable for it. If the $path is not defined, then the modx core path will be used.
              • 3749
              • 24,544 Posts
              LOL. It looks like three of us spent about an hour writing our replies simultaneously.

              I would have to disagree about loadClass() seldom being used. There are 227 instances of it in my core directory and 78 instances of addPackage(). I was surprised to see that there are 301 instances of getService(), though it appears most of them are for built-in services like smarty, registry, mail, error, and lexicon.

                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
                • 42602
                • 81 Posts
                Heh, looks like my caffeine level in blood was too high and read pretty much everything reversed X). My bad Bob smiley
                  • 42602
                  • 81 Posts
                  Ok run some arbitrary tests as was interested about the situation and left the getService out as it differs from addPackage and loadClass completely.

                  I small model with two objects (cnpView, cnpSetting) that extend xPDOSimple object, run next snippet in modx instance:
                  <?php
                  $out['classes'][] = get_declared_classes();
                  $out['classMap'][] = $modx->classMap['xPDOSimpleObject'];
                  $out['packages'][] = $modx->packages;
                  $out['map'][] = $modx->map;
                  
                  $class = $modx->loadClass('cnpView', $modx->getOption('cnpviews.core_path') . 'model/cnpviews/');
                  $out['classes'][] = array_diff(get_declared_classes(), $out['classes'][0]);
                  $out['classMap'][] = array_diff($modx->classMap['xPDOSimpleObject'],$out['classMap'][0]);
                  $out['packages'][] = array_diff_assoc($modx->packages,$out['packages'][0]);
                  $out['map'][] = array_diff_assoc($modx->map,$out['map'][0]);
                  
                  $modx->addPackage('cnpviews', $modx->getOption('cnpviews.core_path') . 'model/', 'cnp_');
                  $out['classes'][] = array_diff(get_declared_classes(), $out['classes'][0]);
                  $out['classMap'][] = array_diff($modx->classMap['xPDOSimpleObject'], $out['classMap'][0]);
                  $out['packages'][] = array_diff_assoc($modx->packages,$out['packages'][0]);
                  $out['map'][] = array_diff_assoc($modx->map,$out['map'][0]);
                  
                  unset($out['classes'][0]);
                  unset($out['classMap'][0]);
                  unset($out['packages'][0]);
                  unset($out['classes'][0]);
                  
                  return "<pre>" . print_r($out, true);


                  Truncated results:
                  Array
                  (
                    [classes] => Array
                      (
                        [1] => Array
                          (
                            [218] => cnpView
                            [219] => cnpView_mysql
                          )
                        [2] => Array
                          (
                            [218] => cnpView
                            [219] => cnpView_mysql
                          )
                      )
                    [classMap] => Array
                      (
                        [1] => Array
                          (
                          )
                        [2] => Array
                          (
                            [24] => cnpView
                            [25] => cnpSetting
                          )
                      )
                    [packages] => Array
                      (
                        [1] => Array
                          (
                          )
                        [2] => Array
                          (
                            [cnpviews] => Array
                              (
                                [path] => /usr/local/apache2/htdocs/cnp/cnp/core/components/cnpviews/model/
                                [prefix] => cnp_
                              )
                          )
                      )
                    [map] => Array
                      (
                        [1] => Array
                          (
                            [cnpView] => Array
                              (
                                ...
                              )
                          )
                        [2] => Array
                          (
                            [cnpView] => Array
                              (
                                ...
                              )
                          )
                      )
                  )


                  In simple,
                  Step #1: Only loaded cnpView class with loadClass. The class and the _mysql version of the class will be loaded so can be accessed with new cnpView() or new cnpView_mysql(). Also the map for the class is loaded which has all the field and relation data. Accessing cnpView is now possible with all methods like newObject etc... accessing cnpSetting is not possible.
                  [edit] Also loadClass return the class, so in this case would be accessible with new $class($modx); instead of new cnpView(); [/edit]

                  Step #2: Loaded the package cnpViews. Now the package is added to internal packages array thus enabling the use of all model objects with queries. Also classMap for xPDOSimpleObject now has the classes set. addPackage itself does not load classes to be accessible with new keyword, nor loads map for object fields. This is handled with loadClass like shown in step #1 (or internally with getObject, newQuery.... methods). addPackage does not declare classes, so they would not be available with new keyword.

                  Hope I made any sense, wrote this for hour and had loads of different tests but all made simply no sense smiley
                  [ed. note: dunnock last edited this post 13 years, 5 months ago.]
                    • 4172
                    • 5,888 Posts
                    As soon as you use any of this xPDO-methods : newQuery, newObject, getObject, getCollection and some others and you have used addPackage before doing so, 'loadClass' is called internally either in the xPDO-method direcly or inside of the xpdoobject - class
                      -------------------------------

                      you can buy me a beer, if you like MIGX

                      http://webcmsolutions.de/migx.html

                      Thanks!
                      • 42602
                      • 81 Posts
                      Just remembered a good use case with loadClass(). As addPackage does not load classes themselves; if extending own xPDOObject or using single inheritance functionality, the parent class is not loaded automatically. Example:

                      //myclass.class.php
                      class myClass extends myParentClass {}
                      
                      


                      //snippet
                      $modx->addPackage('mypackage', $path);
                      $modx->getObject('myClass'); // <-- this would end with fatal error "Class 'myParentClass' not found in ..."
                      
                      


                      To make snippet to work the it has to have loadClass() method for myParentClass

                      //snippet
                      $modx->addPackage('mypackage', $path);
                      $modx->loadClass('myParentClass');
                      $modx->getObject('myClass'); // <-- Works purrrfectly