Stand With Ukraine. Stop Putin. Stop War.

One of the changes in MODX 2.2 is a completely refactored class-based processor system, allowing you to create processors for components more easily than ever. As with everything new and shiny, there's some nice tricks you can use.

I've been using these class-based processors in my latest project, and want to share my experience with it so far, as well as showing some actual, functional code.

What's a class-based processor?

If you've gone through the Developing an Extra tutorial before, you'll know what a processor is: the php code that gets executed by your AJAX connector (or modX::runProcessor) which does whatever needs to be done, and returns JSON.

The class-based part of this all means you can simply use something like the following for a custom "update" processor:

<?php
class fdmPriceCodeUpdateProcessor extends modObjectUpdateProcessor {
    public $classKey = 'fdmPriceCode';
    public $languageTopics = array('frontdeskman:pricing');
    public $objectType = 'fdmp';
}
return 'fdmPriceCodeUpdateProcessor';

... and the MODX core will handle the rest. :) Another example of a class based processor can be found in my What's new in MODX 2.2 post.

What the modObjectUpdateProcessor Process does

Despite the low amount of code we've used, there's a lot of work being done behind the scenes to make sure your update (or other process) is executed like it should. By inspecting the modProcessor class, here's the 14-step process I found for the modObjectUpdateProcessor.

  1. Your processor gets instantiated on the request, properties are being set.
  2. The checkPermissions() function is run which you can use to verify any custom permissions are enabled.
  3. The getLanguageTopics() function is run, which should return an array of lexicon topics to load, which are then loaded.
  4. Processor function initialize(): gets the primary key (from the public variable primaryKeyField which defaults to "id"), gets the object based on the classKey and posted primary key value and if it's an extension of modAccessibleObject it checks the user session against the "save" policy. If the initialialize function doesn't return true, the request is cancelled with an error message.
  5. Processor function beforeSet() is run: by default that function only checks if there are any errors set (by $this->addFieldError('fieldname','error') for example). If this doesn't return true, a failure is returned by the processor.
  6. fromArray() is called on the object (available as $this->object) with all posted properties.
  7. Processor function beforeSave() is run. As with the beforeSet function, this only checks if any errors were registered. If the result is not a boolean true, the processor returns a failure with the value returned as message.
  8. validate() is called on the object. If this doesn't return a positive value, the error messages are set as field errors.
  9. If you set a beforeSaveEvent variable on the class, that event is invoked and if that doesn't return true the save is prevented. While the fireBeforeSaveEvent() function is marked as public, you will probably not want to override this in regular cases.
  10. The saveObject() function is run which simple calls save() on the object. According to the inline comments, this may be overriden for transient and non-persistent object updating.
  11. The fireAfterSaveEvent() function is run, which fires the event you specified in the afterSaveEvent class variable.
  12. The logManagerAction() function is run, which logs a generic message to the Manager Log (available under Reports > Manager Actions).
  13. A success message is returned through the cleanup() function, this actually wraps the pre-2.2 $modx->error->success/failure often used in processors.
  14. The result (success or failure) is parsed into a modProcessorResponse object and returned.

If you look backup, you'll see a lot of bold text. These functions or variables are available for you to override and extend to force it into doing what you need it to do. Note that every processor class (for create, update, getlist, remove and get) has its own functions. If you use an IDE like PhpStorm you can get code hinting, or just dive into the source!

Extending our Processor class to handle Checkboxes

When you've made a beautiful modExt/ExtJS window, and sticked your "active" checkbox (xtype: 'checkbox') in there, you may not exactly get the results you were expecting. Perhaps your schema expects a boolean "true" or "false" (I know mine does!) which would be stored as a tinyint 1 or 0 in the database.. but the form posts a string value "on" if the field is selected, and an empty value if not. Uh oh..

Luckily we've got so many functions we can add to our update processor class to make sure it parses those values into what the object's fromArray (or set) function needs to save it properly.

If you look back to our 14-point list above, you'll see fromArray is executed in step 6, meaning we need to change our property its value before step 6. How does the "beforeSet" function in step 5 sound to you? Sounds like it would work, to me!

If you're not at all familiar with object oriented programming, all the stuff explained so far may sound a bit daunting, but all that we're really doing is overwriting the functions of the original class to do what we want. There's plenty of PHP OOP tutorials on the web should you want to learn more about that.

So, with beforeSet and our expected and needed values in mind, here's our new and improved class which properly handles checkboxes in a MODX 2.2 style processor.

<?php
class fdmPriceCodeUpdateProcessor extends modObjectUpdateProcessor {
    public $classKey = 'fdmPriceCode';
    public $languageTopics = array('frontdeskman:pricing');
    public $objectType = 'fdmp';

    public function beforeSet() {
        $this->setProperty('active',($this->getProperty('active') == 'on'));
        return parent::beforeSet();
    }
}
return 'fdmPriceCodeUpdateProcessor';

I've put it all on one line using a ternary operator and the Processor's getProperty and setProperty functions, but if you prefer a bit more verbose coding, you could also do just this which works exactly the same:

/* ... */
    public function beforeSet() {
        $active = $this->getProperty('active');
        if ($active == 'on') { $active = true; }
        else  { $active = false; }
        $this->setProperty('active',$active);
        return parent::beforeSet();
    }
/* ... */

We could also do something similar in a remove processor, to prevent removing something if it's still linked to another thing. For example the following Processor which prevents removing a "fdmCharacteristic" object if there are still related "fdmRoomTypeCharacteristic" objects.

<?php
class CharacteristicRemoveProcessor extends modObjectRemoveProcessor {
    public $classKey = 'fdmCharacteristic';
    public $languageTopics = array('frontdeskman:property');
    public $objectType = 'rtc';
    
    public function beforeRemove() {
        $chars = $this->modx->getCount('fdmRoomTypeCharacteristic',array('characteristic' => $this->getProperty('id')));
        if ($chars > 0) {
            return $this->modx->lexicon('rtc.remove.roomtypesStillLinked');
        }
        return parent::beforeRemove();
    }
}
return 'CharacteristicRemoveProcessor';

And the final example for today extends the modObjectGetListProcessor to join a table and filter on properties, which is done by extending the prepareQueryBeforeCount function. This one also has a couple more variables we're assigning:

<?php
class RoomTypeGetListProcessor extends modObjectGetListProcessor {
    public $classKey = 'fdmRoomType';
    public $languageTopics = array();
    public $defaultSortField = 'name';
    public $defaultSortDirection = 'ASC';
    public $objectType = 'fdmRoomType';

    /**
     * @param xPDOQuery $c
     * @return \xPDOQuery
     */
    public function prepareQueryBeforeCount(xPDOQuery $c) {
        $query = $this->getProperty('property');
        if (!empty($query) && is_numeric($query)) {
            $c->innerJoin('fdmRoomTypeProperty','RTP','fdmRoomType.id = roomtype');
            $c->where(array(
                'RTP.property' => (int)$query
            ));
        }
        return $c;
    }
}
return 'RoomTypeGetListProcessor';

A note on Checkbox fields in modExt

I tend to use the "checkbox" xtype, however as of MODX 2.1 there is also a special "xcheckbox" xtype which always posts a value (1 or 0 if I remember correctly). I'm not sure why I don't use it, but it might be that the class-based processors do properly function with that one. Just didn't check, and I like being in control. :)

"Just wondering.. Are you working on a Property Management component?"

How'd you know?! :)

Was it the "fdmRoomType", "fdmRoomTypeCharacteristic" or "fdmProperty" object that gave it away? :)

This super-duper awesome extra I am working on is called FrontDeskMan and I hope to finish it over the coming few weeks. It will not become available as an open source project however, but most likely will be a licensed and properly supported Extra for the Hospitality industry as a joint op between myself and Jared Loman. You should totally start warming up some local Hotel clients for a new website with integrated property management and reservations for when we launch!

goldsky

As usual, great post.

John-Paul DeVries

You know, there's this one website, that this one guy referred me (you might know him) that FrontDeskMan might be a good fit for. Have you guys started licensing it yet?

Mark Hamstra

Assuming I have a bit of an idea off what project you are talking about, I wouldn't be 100% sure FDM is the right way to go. The launch is currently postponed to the long run and I don't have an ETA for it at this point.

Comments are closed :(

While I would prefer to keep comments open indefinitely, the amount of spam that old articles attract is becoming a strain to keep up with and I can't always answer questions about ancient blog postings. If you have valuable feedback or important questions, please feel free to get in touch.