As of MODX Revolution 2.2, developers are handed class-based processors to speed up development of the back-end components. These are great, and I have blogged about Class Based Processors in general before with some quick examples, but in this article we'll dive into a particularly awesome one: modObjectGetListProcessor.

The modObjectGetList processor is mostly used for populating grids through the modExt Grid implementation, but you could also use it for any other widget that uses a JSON Data Store. And processors aren't limited to being used by connectors for back-end components.. they're also great to keep your code DRY (Don't Repeat Yourself) for use in Snippets!

For this article we'll assume a simple grid though. The techniques displayed can be used to point you in the right direction for other implementations.

The Basics

Here's basically the minimum processor file you can use:

<?php
class fdmPaymentGetListProcessor extends modObjectGetListProcessor {
    public $classKey = 'fdmPayment';
    public $defaultSortField = 'initiatedon';
    public $defaultSortDirection = 'desc';
    public $objectType = 'fdmPayment';
}
return 'fdmPaymentGetListProcessor';

.. and the reason they're so awesome. Brief, super awesome, working code!

The most important thing to note is the public variable $classKey, this is the class name of the object you are going to retrieve. Furthermore you'll see we define the $defaultSortField to the "initiatedon" date field from the schema, and with $defaultSortDirection we make sure we get the latest on top. The $objectType variable is not necessarily required, but allows you to use prefix (lexicon) error messages for some default sanity checks. For example in the update processor it will use the objectType to prefix _err_ns if the primary key is not specified.

We also make sure we return the name of our extended class in the end, as that is used to instantiate the processor when it's called. While you're free to name it whatever you want, I'd advise you to keep it the same as your classKey. That way, when adding a new processor, you can just copy/paste another one and find/replace the old classKey for the new one and you're good to go.

Exploring the Processor Process

Just like an earlier post, dealing with the modObjectUpdateProcessor class based processor, I have created a list of what happens in the processor that you will find below.

  1. Processor instantiated, properties being set.
  2. Using checkPermissions() the processor decides if the user is allowed to access it.
  3. The processor finds lexicon topics to load via getLanguageTopics, which gets its data from the languageTopics variable (as an array).
  4. initialize() is called on the processor, which sets a number of default properties including sort to the defaultSortField (default: name) class variable, and the direction to the defaultSortDirection variable (default: ASC).
  5. process() is called.
  6. beforeQuery() is triggered by process(), and if the result is not a boolean TRUE it will consider the processor to have failed and cancel further execution.
  7. getData() is triggered by process().
  8. The getData() method builds an xPDOQuery object for the classKey type.
  9. The getData() method calls prepareQueryBeforeCount(xPDOQuery $c) allowing you to add additional conditions to the queries. After calling that, it fetches the total amount of results using modX.getCount.
  10. prepareQueryAfterCount(xPDOQuery $c) is called by getData().
  11. The query is sorted with help of the getSortClassKey() method, and the sortAlias, sort and dir properties.
  12. If the limit property is larger than 0 it limits the query and sets an offset.
  13. modX.getCollection is called with your data, it's been retrieved.
  14. Every row is iterated over using the iterate(array $data) method. iterate calls beforeIteration(array $list), and starts looping over the rows.
  15. If the checkListPermission variable is true, the object extends modAccessibleObject and checkPolicy('list') is false, it skips the row.
  16. prepareRow(xPDOObject|modAccessibleObject $object) is called which needs to return an array with the objects' fields. Great method to customize the retrieved data. The array is added to the list.
  17. After iteration over the entire result set afterIteration(array $list) is called.
  18. The data is returned.

Example Usages

Defining Constraints (where field X has value Y)

When adding constraints, we will take our minimum processor and add (actually override) a new function called prepareQueryBeforeCount. This function takes in the xPDOQuery object as parameter, and expects it to be returned as well.

class fdmPaymentGetListProcessor extends modObjectGetListProcessor {
    public $classKey = 'fdmPayment';
    public $defaultSortField = 'initiatedon';
    public $defaultSortDirection = 'desc';
    public $objectType = 'fdmPayment';

    public function prepareQueryBeforeCount(xPDOQuery $c) {
        $reservation = (int)$this->getProperty('reservation',0);
        $c->where(array('reservation' => $reservation));
        return $c;
    }
}

Easy enough we first get the "reservation" value using $this->getProperty(). By specifying a second value we are assigned a default instead of NULL. In this case I'm setting the default value to zero, which makes sure that if there is no reservation passed, it will not return any results - but no results instead (as all rows have a reservation set to > 0).

After getting the reservation variable, we just interact with the xPDOQuery $c as we would in a normal processor (or script) and pass our where condition.

In the end we return the xPDOQuery (this is important!) and we've limited our query to just that reservation.

Modifying the way row data is returned

In some cases, your database set up may not completely match how you want to display that data in the front end. For example, you may have an array (which is stored serialized), which you want returned as one line of text per array key=>value, for rending in a textarea for example.

<?php
class fdmPaymentMethodGetListProcessor extends modObjectGetListProcessor {
    public $classKey = 'fdmPaymentMethod';
    public $defaultSortField = 'name';
    public $defaultSortDirection = 'asc';
    public $objectType = 'fdmPaymentMethod';

    public function prepareRow(xPDOObject $object) {
        $ta = $object->toArray('', false, true);
        $props = '';
        foreach ($ta['properties'] as $prop => $value) {
            $props .= $prop . ':' . $value . "\n";
        }
        $ta['properties'] = $props;
        return $ta;
    }
}
return 'fdmPaymentMethodGetListProcessor';

You will also see that instead of calling simply $object->toArray(), I am passing some additional parameters.

Specifically selecting fields

<?php
class fdmTermsGetListProcessor extends modObjectGetListProcessor {
    public $classKey = 'fdmTerms';
    public $defaultSortField = 'name';
    public $defaultSortDirection = 'ASC';
    public $objectType = 'fdmTerms';

    public function prepareQueryBeforeCount(xPDOQuery $c) {
        $c->select(array(
            'fdmTerms.id',
            'fdmTerms.name',
            'fdmTerms.summary',
            'fdmTerms.lastupdated',
            'fdmTerms.cancellable',
            'fdmTerms.cancelperiod',
        ));
        return $c;
    }

    public function prepareRow(xPDOObject $object) {
        $ta =  $object->toArray('', false, true, true);
        $ta['lastupdated'] = ($ta['lastupdated'] > 0) ? date('Y-m-d',$ta['lastupdated']) : '';
        return $ta;
    }
}
return 'fdmTermsGetListProcessor';

You could also join tables in the prepareQueryBeforeCount processor, add additional constraints etc.

Are there any more examples you would like to see, or have some to share yourself? Let me know in the comments!

Good job Mark! Thanks a lot!!

theredpin.com theredpin.com
Jul 04, 2012 at 03:31 PM

What's up everyone, it's my first pay a visit at this web site, and article is really fruitful for me, keep up posting these types of articles or reviews.

Mark, this is great, thanks! I am defiantly getting a better understanding of this.

Is there a preprocessor/function that allows you to join tables together before data is sorted/filtered/returned?

Yep, just use the prepareQueryBeforeCount() method to join tables and use prepareRow() to get the joined info into the returned array. Assuming you used select specifically, you can do that by passing some options to toArray() as per the last example in the article.

Hi Mark,

I tried to select only two fields of my custom table (id,name) and did so using the select property of the prepareQueryBeforeCount method. But returned are always all fields in Json.

How can I restrict the Json Data to only 2 fields?

Martin

Also add the prepareRow method:

public function prepareRow(xPDOObject $object) {
$ta = $object->toArray('', false, true, true);
return $ta;
}

OK - this works now! Thank you!
Seems so easy - but how can one figure out how to do such things? It's so hard to find this in documentation or examples.

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.