Creating your own MODX System Events

How to create custom System Events for use with your code


In my previous article, we looked at how to fire (invoke) MODX system events in your code. In this one, we'll look at creating and firing your own custom system events.


MODX logo

New System Events

There's no way (at this time) to create new system events in the MODX Manager, so you have to do it in code. Luckily, that's fairly simple. It's done the same way you create any MODX object. You instantiate the object with $modx->newObject(), set its fields, and save it.

Suppose that you've developed an e-commerce site and you store your customers to a custom database table. You've created a Customer class with various methods. You know that in the future, you'll want certain things to happen when a customer is created or updated, but you're not sure what they are, or you know some of them but more may be added in the future.

For example, you might want to look at the customer's history and give certain customer preferred status, and/or send them an email with special offers. You might want to offer suggestions (a la Amazon) based on what other customers with the same buying patterns have ordered and save the suggestions to another table (indexed by customer ID) for the next time the customer logs in.

You could build the code for those features into the save() method of your Customer class, but that would mean rewriting that class code every time you add a new feature. It's a lot cleaner to just create your own OnCustomerSave event that fires whenever a customer is saved. That way, you can attach as many plugins as you want to that event and have them execute whenever a customer is saved.


Creating a New System Event

The system event object (modEvent) has only three fields and two of them are optional (it's one of the few MODX objects that has no id field):

  • name (string) — The name of your event; by convention, it should begin with On
  • service (integer) — Always 1 (optional)
  • group (string) — Name of the group containing your events (optional)

To be honest, I don't know what the service field is intended for, but I've never seen it with any other value than 1.

The group field is optional, but it can help users find the event on the System Events tab when editing your plugin, even if you only have one event. There is a "Group" dropdown on that tab and there aren't that many groups, so the event can be connected to a plugin without having to search for it in the long list of system events.

Since you can't create the event in the Manager, here's the code to create two of them:

$eventNames = array(
    'OnBeforeCustomerSave',
    'OnCustomerSave',
);

foreach ($eventNames as $eventName) {

    /* Make sure it doesn't already exist */
    $event = $modx->getObject('modEvent', array('name' => $eventName));

    if (! $event) {
        /* It doesn't exist; create it */
        $fields = array(
            'name' => $eventName,
            'service' => '1',
            'group' => 'MyEvents',
        );

        $event = $modx->newObject('modEvent');
        if ($event && $event instanceof modEvent) {
            $event->fromArray($fields, "", true, true);
            if (!$event->save()) {
                /* Handle save failure error */
                $modx->log(modX::LOG_LEVEL_ERROR, 'Could not save event ' . $eventName);
            }
        } else {
            /* Handle failure to create error */
            $modx->log(modX::LOG_LEVEL_ERROR, 'Could not create event ' . $eventName);
        }
    }

}

The fromArray() method is a shorthand for this code:

foreach($fields as $key => $value) {
    $event->set($key, $value);
}

Once you've run the code, you should be able to edit any plugin and see your event on the System Events tab.


Firing Your Event

Before firing your event, you need to figure out what information your plugin(s) might need. In this case, it's likely that sending the $customer object will do it. A plugin can get the id or any other fields from the $customer object with code like this:

$id = $customer->get('id');

You'd make your invokeEvent() calls above and below the $customer->save() line like this:

$saveFields = array(
    'customer' => &$customer,
);

$modx->invokeEvent(OnBeforeCustomerSave, $saveFields);
if ($customer->save()) {
    $modx->invokeEvent(OnCustomerSave, $saveFields);
} else {
    /* Handle save failure error */
    $modx->log(modX::LOG_LEVEL_ERROR, 'Could not save customer object ');
}

Using &$customer sends the object by reference. This is only important if your plugin code might want to modify the $customer object and save it. If you're sure that will never happen, you can use $customer.


Connecting a Plugin to Your Event in the Manager

This is done the same way you'd do it for any plugin and event. Just edit the plugin and check the event name on the System Events tab. While you're there, you can assign a property set to the plugin (assuming that the property set already exists), and set the priority of the plugin. the priority is only important if you have multiple plugins attached to the same event and want them to execute in a certain order. The lower the number, the earlier the plugin will execute. In other words, a plugin with a priority of 0 will execute before a plugin with a priority of 1.

Instead of creating a separate plugin, you can have one plugin respond to multiple events by connecting more than one event to the plugin and doing something like this:

$event = $modx->event->name;

switch($event) {
    case 'OnBeforeCustomerSave':
        /* Do something */
        break;
    case 'OnCustomerSave':
        /* Do something else */
        break;
    default:
        $modx->log(modX::LOG_LEVEL_ERROR, 'Invalid event in my plugin: ' . $event);
}

Connecting a Plugin to Your Event in Code

If you're creating a transport package, you'll want to make the connection between the event and the plugin when your package is installed. The connection is stored in the modPluginEvent object (in the modx_site_plugin_events table). You'll need to do this in a resolver that runs at the end of your installation so that the event and the plugin already exist.

That code would look something like this:

$eventName = 'OnBeforeCustomerSave';
$pluginName = 'MyPlugin';
$propertySetName = 'MyPropertySet'; // (optional)

$event = $modx->getObject('modEvent', array('name' => $eventName));
$plugin = $modx->getObject('modPlugin', array('name' => $pluginName]));

/* If there's a property set you want to attach (optional) */
/* $propertySetObj = $modx->getObject('modPropertySet',
        array('name' => $propertySetName));
} */


if (!$plugin || !$event) {
    if (!$plugin) {
        $modx->log(xPDO::LOG_LEVEL_ERROR, 'Could not find Plugin  ' .
            $pluginName);
    }
    if (!$event) {
        $modx->log(xPDO::LOG_LEVEL_ERROR, 'Could not find Event ' .
            $eventName);
    }
}
$pluginEvent = $modx->getObject('modPluginEvent', array('pluginid'=>$plugin->get('id'),'event' => $eventName) );

if (!$pluginEvent) {
    $pluginEvent = $modx->newObject('modPluginEvent');
}
if ($pluginEvent) {
    $pluginEvent->set('event', $eventName);
    $pluginEvent->set('pluginid', (integer) $plugin->get('id'));
    /* $pluginEvent->set('priority', (integer) 0);

    /* If there's a property set */
    /* if ($propertySetObj) {
        $pluginEvent->set('propertyset', (integer) $propertySetObj->get('id'));
    } else {
        $pluginEvent->set('propertyset', 0);
    } */

}
if (! $pluginEvent->save()) {
    $modx->log(xPDO::LOG_LEVEL_ERROR, 'Unknown error saving pluginEvent for ' .
        $pluginName . ' - ' . $eventName);
}

MyComponent

MyComponent is a development environment for creating transport packages for MODX extras. It takes a while to learn and set up, so it's probably overkill for a single transport package unless the package is fairly complex, but once you've created the config file for your extra, MyComponent will automatically create your plugins and events (along with almost everything else your extra needs) and add them to your package. It will also automatically create the resolver to connect the plugins to the events and add it to the package as well.


Coming Up

In the next article, we'll look at how to use PHP's comparison and equality operators in MODX.



Looking for high-quality, MODX-friendly hosting? As of May 2016, Bob's Guides is hosted at A2 hosting. (More information in the box below.)



Comments (0)


Please login to comment.

  (Login)