Jump to main content Jump to doc navigation

What is modRegistry?

modRegistry is a message queue service in the MODX core. It ships with a file-based and a database modRegister implementation and can be extended for custom or external queue backends.

A registry holds any number of registers. Within a register you subscribe to topics, then send messages to a topic and read them from it.

Choosing a modRegister implementation

MODX ships with two modRegister implementations: one file-based, one backed by database tables.

modFileRegister

This file-based register reads and writes messages, by default, to files under the registry/ folder in the MODX cache_path location. It suits single-server deployments without heavy traffic, where several requests might otherwise write and read the same topic simultaneously.

Here is how you would add a specific file-based register named food:

<?php
$modx->getService('registry', 'registry.modRegistry');
$modx->registry->addRegister('food', 'registry.modFileRegister', array('directory' => 'food'));

::: warning Clearing the cache manually by deleting all files and folders under core/cache/ deletes existing modFileRegister messages as well. :::

modDbRegister

Choose the database implementation for higher message volumes, or when messages must be shared across a load-balanced set of web server nodes.

Here is how you would add a specific database register named food:

<?php
$modx->getService('registry', 'registry.modRegistry');
$modx->registry->addRegister('food', 'registry.modDbRegister', array('directory' => 'food'));

Using the Registry API

Registry service methods

Besides addRegister(), the modRegistry service itself offers:

  • getRegister($key, $class, array $options = array()) — get an existing register (or create it if missing);
  • removeRegister($key) — remove a register from the registry;
  • setLogging(modRegister &$register, $topic, $level = modX::LOG_LEVEL_ERROR, $clear = false) — route log messages into a register topic (see the "Capturing Log Messages" example below);
  • resetLogging() — stop routing log messages to a register;
  • isLogging() — whether log routing is currently active.

Register methods

Each modRegister instance (file or database) provides, beyond connect(), subscribe(), read() and send() used below:

  • close() — close the register connection;
  • clear($topic) — remove all messages in a topic;
  • unsubscribe($topic) — remove a topic from the subscriptions;
  • setCurrentTopic($topic) / getCurrentTopic() — inspect or change the current topic;
  • begin($transactionKey), commit($transactionKey), abort($transactionKey) — group sends into a transaction;
  • acknowledge($messageKey, $transactionKey) — acknowledge a message within a transaction;
  • getKey() — the register's key.

Connecting

Connect to the register before you use it:

<?php
$connected = $modx->registry->food->connect();

If the connection fails, the return value is false.

Subscribing

Subscribing makes a topic available for sending and reading:

<?php
$modx->registry->food->subscribe("/beer/");

The topic /beer/ is added to your subscriptions and becomes the current topic. Topics work like relative URIs: a leading slash starts from the root of the register, without one you subscribe relative to the current topic. Once you subscribe to at least one topic, you can send and read messages from it at will.

Sending Messages

Sending an Array of Sequenced Messages

Messages sent as a plain array are read back in the same order:

<?php
$modx->registry->food->send("/beer/", array("beer1", "beer2", "beer3"));

Sending an Array of Named Messages

An associative array sends messages under your own keys:

<?php
$modx->registry->food->send("/beer/", array("Heineken" => "not so good", "Pabst Blue Ribbon" => "rocks", "Molson Golden" => "ok for Canadian beer"));

These messages will be read in ascending alphabetic order, or specifically by key.

Sending a Single Message

A single message without a key gets a time-based key from the register, so it is read in the correct time order:

<?php
$modx->registry->food->send("/beer/", "It's Miller Time!", array('kill' => true));

The kill option tells message consumers to stop reading any further messages once a message with this option is read.

Options for Sending Messages

Option Meaning
delay Seconds before the message becomes readable; only for time-based messages (no keys). The message reaches the queue immediately but stays unreadable until the delay expires.
ttl Seconds the message stays valid in the queue. A message past its TTL is left out of read() results.
kill When a consumer reads a message with this option, it stops reading the queue, whatever other read options say.

Reading Messages

Reading options are passed to the read() method.

Polling for New Messages in a Topic

Poll once for up to 5 new messages in the current topic (/beer/):

<?php
$msgs = $modx->registry->food->read(array(
    'poll_limit' => 1,
    'msg_limit' => 5
));

The result is an array of messages in read order, without message keys unless include_keys asks for them.

Options for Reading Messages

Option Default Meaning
poll_limit polls until msg_limit, time_limit or a kill message Number of poll attempts. Set it to 1 in web requests: a single check runs and the read ends regardless of msg_limit/time_limit. Larger values suit a PHP poller (e.g. in a cronjob) that loops until messages arrive.
poll_interval 0 Seconds to wait between polls for new messages.
time_limit — Maximum seconds to keep polling when poll_limit is greater than 1.
msg_limit 5 Maximum number of messages to read from the queue.
remove_read — Remove each message from the queue when it is read.
include_keys (MODX 2.2+) false Return message keys along with the messages; otherwise you get a plain ordered array of messages.

Using the Registry Processors

Besides the raw API, MODX ships connectors and processors for a register topic; components call them with runProcessor, and AJAX requests go through the connectors.

Sending Messages

Parameters of the system/registry/register/send processor; pass them to runProcessor or via request variables to the connector:

Parameter Required Description
register yes Name of the register to send a message to.
topic yes Topic within the register.
register_class no modRegister implementation; default registry.modFileRegister.
message yes Message to send; an empty message fails the processor. With message_format set to json, multiple messages can be sent.
message_key no Key for a named message (MODX 2.2+); empty gives a sequenced timestamp key.
message_format no Default string; with json (MODX 2.2+) the message is converted to PHP before sending, so several or structured messages fit in one call.
delay no Seconds the message stays unreadable (time-based messages only); default 0.
ttl no Seconds the message stays valid in the queue; default 0 (no expiry).
kill no Reading this message stops the consumer; default false.

There is also an API-level option: a direct send() call accepts message_type to influence how the payload is stored (see the modRegister::send() docblock for details).

Reading Messages

The system/registry/register/read processor runs the same way, via runProcessor or the connector:

Parameter Required Description
register yes Name of the register to read from.
topic yes Topic within the register.
register_class no modRegister implementation; default registry.modFileRegister.
format no Default json (MODX 2.2+): messages as a JSON string, handy for AJAX. html_log treats them as xPDO/modX log messages formatted for HTML (modExt consoles). Any other value returns a PHP array.
poll_limit no Poll attempts before exiting; default 1.
poll_interval no Seconds between poll attempts; default 1.
time_limit no Seconds to keep polling; default 10.
message_limit no Maximum messages returned; default 200.
remove_read no Remove returned messages from the queue; default true.
include_keys no Return message keys with the messages (MODX 2.2+); default false.
show_filename no Add the filename to html_log messages; default false.

Examples

Alternative to Session Storage

A register topic stores data independently of session state, so it stays readable after the state changes. Custom registration flows need this: data written before an unauthorized user logs in must survive the new session.

Write a named message into a topic for retrieval by key later:

<?php
$modx->getService('registry', 'registry.modRegistry');
$modx->registry->addRegister('login', 'registry.modFileRegister', array('directory' => 'login'));
$modx->registry->login->connect();
$modx->registry->login->subscribe('/useractivation/');
$modx->registry->login->send('/useractivation/',array($user->get('username') => $pword),array(
    'ttl' => ($modx->getOption('activationttl',$scriptProperties,180)*60),
));

And to retrieve the specific message later:

<?php
$modx->getService('registry', 'registry.modRegistry');
$modx->registry->addRegister('login','registry.modFileRegister');
$modx->registry->login->connect();
$modx->registry->login->subscribe('/useractivation/'.$user->get('username'));
$msgs = $modx->registry->login->read(array('poll_limit' => 1));
$password = reset($msgs);

Capturing Log Messages

Set logTarget to a modRegister instance and every log message goes into the register, where you can read it later for user feedback, audit views, and similar needs.

<?php
$modx->getService('registry', 'registry.modRegistry');
$modx->registry->addRegister('logging', 'registry.modFileRegister', array('directory' => 'logging'));
$modx->registry->logging->connect();
$modx->registry->logging->subscribe($topic);

/* set the logTarget to the register instance */
$oldTarget = $modx->setLogTarget($modx->registry->logging);

/* code here that sends log messages */

/* set the old target back */
$modx->setLogTarget($oldTarget);

Registering Load Balanced Web Nodes for Remote Commands

This example uses two plugins to refresh caches across web nodes that each run their own local file cache.

The first plugin, on OnWebPageComplete, registers its server instance for 20 minutes and then polls for a clearCache command. The second plugin, on OnSiteRefresh, broadcasts clearCache to every registered instance when you refresh the cache in the manager. The 20-minute TTL suits environments where instances launch and stop on demand; remove it to skip the overhead of writing those messages every 20 minutes.

In practice, keep the manager on its own instance (a separate subdomain, for example), away from the public web nodes. Heavy manager traffic then stays isolated, and changes reach the web nodes only when someone runs Refresh site.

The remotecommands plugin (OnWebPageComplete):

<?php
/* RemoteCommands plugin -- register with OnWebPageComplete event */

/* find any remote commands to execute from the master instance */
$instance = $_SERVER['SERVER_ADDR'];
if (!empty($instance) && $modx->getService('registry', 'registry.modRegistry')) {
    $modx->registry->addRegister('remotes', 'registry.modDbRegister', array('directory' => 'remotes'));
    $modx->registry->remotes->connect();

    /* register this instance */
    $modx->registry->remotes->subscribe("/distrib/instances/");
    $modx->registry->remotes->send("/distrib/instances/", array($instance => true), array('ttl' => 1440));

    /* find any valid command messages for this instance and act on them */
    $modx->registry->remotes->subscribe("/distrib/commands/{$instance}/");
    $commands = $modx->registry->remotes->read(array('poll_limit' => 1, 'msg_limit' => 1));
    if (!empty($commands)) {
        $command = reset($commands);
        if (!empty($command)) {
             switch ($command) {
                 case 'clearCache':
                    $modx->cacheManager->refresh();
                    break;
             }
        }
    }
}

The sendclearcache plugin broadcasts the command to each registered instance:

<?php
/* SendClearCache plugin -- register with OnSiteRefresh event */

/* read instances and write clear cache msg to each command directory */
if ($modx->getService('registry', 'registry.modRegistry')) {
    $modx->registry->addRegister('remotes', 'registry.modDbRegister', array('directory' => 'remotes'));
    $modx->registry->remotes->connect();
    $modx->registry->remotes->subscribe('/distrib/instances/');
    $instances = $modx->registry->remotes->read(array('poll_limit' => 1, 'msg_limit' => 25, 'remove_read' => false));
    if (!empty($instances)) {
        foreach ($instances as $instance) {
            if ($instance == $_SERVER['SERVER_ADDR']) continue;
            $modx->registry->remotes->subscribe("/distrib/commands/{$instance}/");
            $modx->registry->remotes->send("/distrib/commands/{$instance}/", 'clearCache', array('ttl' => 1440));
        }
    }
}

Other remote commands travel the same way: handle them in the switch statement of the RemoteCommands plugin.

Support the team building MODX with a monthly donation.

The budget raised through OpenCollective is transparent, including payouts, and any contributor can apply to be paid for their work on MODX.

Backers

  • modmore
  • STERC
  • Digital Penguin
  • Jens Wittmann – Gestaltung & Entwicklung
  • CrewMark
  • Fabian Christen
  • Sepia River Studios
  • Dannevang Digital
  • Alex
  • A. Moreno
  • Chris Fickling
  • Stéphane Jäggi
  • Murray Wood
  • Anton Tarasov
  • JT Skaggs
  • deJaya
  • Lefthandmedia
  • eydolan
  • Following Sea
  • Guido Gallenkamp
  • YJ
  • Raffy
  • Snow Creative
  • Nick Clark
  • Guest
  • Helen
  • krisznet
  • Yanni
  • Richard

Budget

$194 per month—let's make that $500!

Learn more