Using MODX Outside of MODX3

Take advantage of MODX features and functionality in your scripts and applications built alongside MODX3.

By Bob Ray
January 13, 2022
Using MODX Outside of MODX3

In the previous article, we looked at instantiating the $modx object outside of pre-MODX3 installs. In this one, we’ll look at using MODX outside of MODX Revolution 3. Much of the code (and the discussion of it) is the same as the last article. The main difference is that MODX Revolution 3 makes extensive use of namespaces and has an autoloader to load classes.

The Code

<?php
namespace MODX\Revolution;

use xPDO\xPDO;
use MODX\Revolution\Error\modError;

$cliMode = false;
$outsideModx = false;

/* Set a user (set to an actual username) */
$myUserName = 'JoeBlow';

/* Instantiate MODX if necessary */

/* See if we're in MODX or not */
$inModx = isset($modx) && $modx instanceof modX;

/* All the code below will only execute if
   we're not in MODX */
if (!$inModx) {
    $outsideModx = true;

    /* Correct this path to point to the config.core.php file
       in the MODX root directory! */
    require "c:/xampp/htdocs/modx3/config.core.php";

    /* Set path to config.inc.php
       (minus the filename) -- Note that this
       has to be corrected if the config directory
       is not directly under the core directory */
    $configPath = MODX_CORE_PATH .
        MODX_CONFIG_KEY . '/';

    /* Check for CLI mode and set log target */
    if (php_sapi_name() == 'cli') {
        $cliMode = true;
        $logTarget = 'ECHO';
    } else {
        $logTarget = 'HTML';
    }

    /* get the vendor autoload file */
    require_once MODX_CORE_PATH . "vendor/autoload.php";

    /* Constants below have to come after
       getting the autoloader */

    /* Set Log Level; options are:
       LOG_LEVEL_FATAL, LOG_LEVEL_ERROR,
       LOG_LEVEL_WARN, LOG_LEVEL_INFO,
       and LOG_LEVEL_DEBUG  */

  /* (If you don't send a log level,
     MODX will set it to LOG_LEVEL_ERROR) */
  $logLevel = xPDO::LOG_LEVEL_INFO;

  $options = array(
    'log_level' => $logLevel,
    'log_target' => $logTarget,
  );

     /* instantiate the $modx object */
    $modx = new modX($configPath, $options);

    if (!$modx instanceof modX) {
        die('Could not create MODX class');
    }

    $modx->initialize('web');
    // $modx->initialize('mgr');

    /* Initialize $modx->error */
    if (!$modx->services->has('error')) {
        $modx->services->add('error', new modError($modx));
    }
    $modx->error = $modx->services->get('error');

    /* Make sure there's a request */
    $modx->getRequest();

    /* Set $modx->resource to home page */
    $homeId = $modx->getOption('site_start');
    $homeResource = $modx->getObject('modResource', $homeId);

    if ($homeResource instanceof modResource) {
        $modx->resource = $homeResource;
    } else {
        die('No Resource');
    }

    /* set $modx->user */
    $myUser = $modx->getObject('modUser',
        array('username' => $myUserName));
    if ($myUser instanceof modUser) {
        $modx->user = $myUser;
    } else {
        die('No User');
    }
}

Making Sure It Works

As with the code in the earlier article on using MODX outside of MODX2, once you’ve created a file with the code above, you can execute it from the command line, or in a code editor. If it runs without throwing an error, it works.

Namespaces

MODX Revolution 3, unlike MODX2 and earlier, makes extensive use of namespaces. A full explanation of namespaces is beyond the scope of this article, but we’ll say a little about them here.

namespace MODX\Revolution;

use xPDO\xPDO;
use MODX\Revolution\Error\modError;

The namespace used for all of MODX is MODX\Revolution. This makes sure that any code that refers to a MODX object or method is using the right object, rather than one with the same name in a MODX Extra, a custom Plugin or Snippet, or some library used by MODX (e.g., mail(), cURL or guzzle).

The two use statements tell PHP where to find the xPDO class and the modError class, so we don’t have to include those class files. Notice that unlike MODX2, we also don’t have to include the modX class file.

This magic all works with the help of the autoloader, which we load before using the xPDO constants for the log level and before we create the $modx object. Once we’ve “included” the autoloader file, we can use almost any MODX object by name without having to include its class file.

Creating the $modx Object

To create MODX we use this line:

$modx = new modX($configPath, $options);

We send the MODX constructor two arguments. The first ($configPath) is the path to the MODX config file (config.inc.php), minus the file name. The second ($options) is the $options array we set up earlier containing the log settings. Both arguments are optional, but it’s more efficient to send them, since otherwise MODX may make several attempts to find things, and it will often set the wrong log settings for our purposes.

If we don’t send the $options argument, we’d also have to call $modx->setLogTarget() and $modx->setLogLevel() later in our code to make sure they were set correctly, which means they’d both be called twice, once in the constructor and again in our code.

Do I Really Need All That?

The comments in the code above are fairly self-explanatory, but they don’t explain why were doing what we’re doing and when it’s necessary.

Not all of the code is necessary in every case, although using all of it will very seldom cause any trouble. For a simple utility Snippet, you often need only the first part of the code (up to and including the $modx->initialize() line).

Initializing MODX

The first part of the code, checks to see whether the $modx object already exists. There’s no point in creating it if it’s there already.

The choice of whether to initialize MODX in the 'web' or 'mgr' context depends on what your code does. Most of the time, it doesn’t matter, but if your code calls processors that check permissions or if your code checks permissions directly, MODX will use the Policy tied to the user’s user group for the specified context when doing the checks.

For security purposes, using 'web' is generally safer since the user is less likely to have permissions in that context that will let them cause trouble.

$outsideModx and $cliMode

The script above sets the $outsideModx variable just in case we need to know later on in the script whether we’re running inside MODX or not. We also set the $cliMode variable. This indicates whether the script is running from the command line, or in a browser. We also set the MODX log target so $modx->log() messages will be formatted properly.

Inside MODX, $cliMode will always be false, which is why the check is inside the section that only runs outside of MODX (and why we set it to true above that section). When running outside of MODX, $cliMode will be true only if the script is run from the command line or from inside a programming editor, and false if it is running in a browser. This is important to know because in CLI mode, HTML code will be displayed rather than executed and will clutter up the output.

If you’re running in CLI mode and need to print an error message, for example, you might use code like this:

    if ($cliMode) {
        echo "\nFile not found";
    } else {
        echo '<br>File not found';
    }

The code above assumes that your code file is inside the MODX install, though it doesn’t have to be at the root. Surprisingly, the code will still work if your code is in another directory altogether as long as the paths are correct. In this case, your code editor (e.g., PhpStorm) will flag a bunch of classes use statements, and namespace statements as errors because it can’t find them, but the code will still work.

If you have lots of output, you’d probably use a function to print messages, possibly using nl2br() to convert newlines to break tags.

$error, $request, $resource, and $user

It’s difficult to know for sure when these will be necessary. You might be doing things in your script that will access various MODX objects directly or indirectly. If you call a processor, for example, with $modx->runProcessor(), the odds are good that the processor will fire an event or two. In many event invocations, $modx->resource or $modx->user get sent along for the ride. If they don’t exist, the program may crash.

You might think these settings are unnecessary if you’re not calling any processors or invoking any events, but remember that the code is running as if it’s in MODX. That means any of the site’s Plugins might come into play. One of them could be checking $modx->user or $modx->resource. It could also be looking at the current request or throwing an error using the MODX error handler.

Every one of the four settings has crashed code of mine when a processor or Plugin tried to access them, and they weren’t set.

The code above sets $modx->resource to the page set in the site_start System Setting. If your code will be made public, this is a safe bet because every site has one. For private code, you might want to create a dummy resource so Plugins won’t have a chance to mess with the home page (e.g., the DefaultResourceGroup Plugin).

If your code doesn’t use Resources, you can remove that section.

If you use Resources, but don’t save them, you can probably remove that section, but be prepared to put it back if the code crashes.

You’ll have to modify the code above to change “JoeBlow” to the username of an actual user. For security purposes, it’s best not to set this to the admin Super User unless you need that user’s permissions to execute your code. If you will be distributing your code, you’ll have to make a decision about whether to use the admin (ID = 1), create a user, pick a random user, or advise people to set the username themselves.

You might be tempted to use the (anonymous) user, but that’s not a real user and the getObject() call will fail. You can set the user to the (anonymous) user by replacing all the user code above with the simple call below, but be aware that the user’s permissions may be too limited for your use case.

$modx->getUser();

Note that if you’re running in CLI mode, permissions may not an issue. In CLI mode, MODX bypasses many permission checks on the assumption that if you have shell access, you’re a reputable character. If you’re running in MODX or in a browser, though, and you call any MODX processors, they will balk if the user doesn’t have the necessary permissions. In those cases, Plugins may also do permission checks.

As with $modx->resource, if your code doesn’t involve users at all you can remove that section and if you don’t save any users, you can probably remove it.

Summing Up

In practice, I generally use all the code above. The time penalty is minimal, and you never know when a new Extra, Plugin, or Snippet is going to require some part of it. I put the code in a file called initialize-modx.php and “include” it at the top of my code.


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.