Creating Extras for Revo 3 Part 4: Objects in Code

Handling MODX objects in the code of your Extra.

By Bob Ray
May 23, 2022
Creating Extras for Revo 3 Part 4: Objects in Code

In the previous article, we looked at how to deal with Resources and users in a Transport Package. In this one, we’ll look how to handle MODX objects in the code of your Extra.

Separate Packages

If you maintain two different packages, one for Revo 2 and one for Revo 3, handling objects is pretty simple. As we mentioned in an earlier article in this series, you just need to add a use statement for each object type. Because the use statements are interpreted at compile time, before the code executes, they can theoretically go anywhere in your code. By convention, though, they belong at the very top of your code, after the PHP tag, but before any code.

The PHP docs refer to this process as “importing”, but that term is misleading. It implies that the class is imported as it would be with include or require. It is not. To use the class in your code, it still needs to be included or required, unless there is an autoloader involved, which does the require or include for you.

MODX has a complete built-in autoloader, so for MODX objects like Plugins, Chunks, Snippets, etc., all you need to do is add the correct use statements. All each use statement does is create an alias for the class.

Because Revo 3 uses namespaces, objects like Resources and Elements need the namespace prefix to autoload the correct class.

So if your code uses modChunk in a call like getObject() or getCollection(), you can do this in Revo 3:

$chunk = $modx->getObject('MODX\Revolution\modChunk', array('name' => 'chunkName'));

You won’t see that in the MODX code, though because it’s simpler to place a use statement above the call like this:

/* At the top of the file: */
use MODX\Revolution\modChunk

/** anywhere in the code: */

$chunk = $modx->getObject('modChunk', array('name' => 'chunkName'));

When PHP sees modChunk in the call to getObject() it knows that you mean MODX\Revolution\modChunk. In other words, the use statement makes modChunk an alias for MODX\Revolution\modChunk.

The only catch is that you have to find all the MODX objects used in your code. If your code editor can do a regular expression search, you can search for the pattern ["']mod[A-Z] with case-sensitivity turned on. Note that this won’t find calls where the class of the object is in a variable, like this:

$objects = array(
    'modChunk',
    'modPlugin',
    'modSnippet',
);

foreach ($objects as $object) {
   $totalCount += $modx->getCount($object);
}

The search will find the objects in the array, though, so it will still help you find them.

Another way to find the objects: You can just run your code and check the Deprecations Log tab of the Error Log in the Manager (assuming that the log_deprecated System Setting is set to “Yes”). The log will list all the objects used in your code. Then you can search the code for each object class in turn. Note that, at this writing, the Deprecations Log is not always updated immediately after you run your code, even if you click on the “Refresh” button. You often need to “re-launch” the log by going to Manage —> Reports —> Error Log to get the deprecation notices to show up.

Single Package

It’s easy enough to find out what version of MODX is running (we’ll see how in a bit), so you might think you could just check for the version and throw in the appropriate use statements if it’s Revo 3 or greater. Unfortunately, the use statements are not evaluated at run time, and putting the use statement in regardless of the MODX version with crash Revo 2 when it can’t find the namespaced classes.

If you want to create a single package for your Extra that will run in both Revo 2 and Revo 3 without throwing any deprecation errors, the process is still fairly simple, but more time-consuming. You need to find every call that uses the class of a MODX object and add a class prefix that will only be added in Revo 3, like this:

$isMODX3 = $modx->getVersionData()['version'] >= 3;
$classPrefix = $isMODX3 ? 'MODX\Revolution\\' : '';

/* Later in the code: */
$doc = $modx->getObject($classPrefix . 'modResource');

If you’re not familiar with the PHP ternary operator, here’s the more verbose equivalent of the second line above:/p>

  if ($isMODX3) {
      $classPrefix = 'MODX\Revolution\\';
  } else {
      $classPrefix = '';
  }

With the code above, if we’re not running Revo 3 or higher, $classPrefix will hold an empty string so are reference to $classPrefix . 'modChunk' will be evaluated as 'modChunk'.

This will work for almost all MODX objects, except for modProcessor, modTransportPackages, modTransportProvider, modMediaSource, and modPhpMailer.. We’ll discuss those in later articles.

Finding the Calls

There are several ways to approach to finding the spots where you need to add the class prefix. There are the methods described above—using the Deprecation Log, and the regular expression search. Another method is to do a case-sensitive search for the most common calls that might need changing:

Search for getObject to find
    getObject
    getObjectGraph
Search for getCo to find
    getCollection
    getCollectionGraph
    getCount
Search for new to find
    newObject
    newQuery
    new
Search for getIterator

Ignore what you find in the build script and related files except for resolvers and validators.

Note that no changes are necessary for getChunk(), getOne(), and getMany(). The getChunk() method has the correct class key built in. The getOne(), and getMany() methods use the same aliases in Revo 2 and Revo 3 (e.g., 'Profile', 'User', 'Template').

Class Prefixes Inside Classes

If your code is in a class, the approach is slightly different. First, add a class variable called $classPrefix, then in the class constructor, or an initialize() method that’s always called (in the constructor or from outside the class), or in a setPrefix() method, use this code:

$isMODX3 = $modx->getVersionData()['version'] >= 3;
$this->classPrefix = $isMODX3 ? 'MODX\Revolution\\' : '';

Then, anywhere in your code, add $this->classPrefix ahead of any class name.

Here’s a more complete example:

class MyClass {
   $classPrefix = '';
   $modx = null;

   public function __construct($modx) {
        $this->modx = $modx;
        $isMODX3 = $modx->getVersionData()['version'] >= 3;
        $this->classPrefix = $isMODX3 ? 'MODX\Revolution\\' : '';
   }

   public function someMethod() {
       $doc = $this->modx->getResource($this->classPrefix .
            'modResource', array('pagetitle' => 'Home Page'));
   }

}

Bob Ray is the author of the MODX: The Official Guide and dozens of MODX Extras including QuickEmail, NewsPublisher, SiteCheck, GoRevo, Personalize, EZfaq, MyComponent and many more. His website is Bob’s Guides. It not only includes a plethora of MODX tutorials but there are some really great bread recipes there, as well.