Why Extend modResource?

Realize amazing speed increases by extending the modResource object in MODX

In the previous article, I discussed the hows and whys of extending the modUser object. In this one, I want to do the same for the modResource object. This article will cover a lot of the same ground that the previous one did, because I can't be sure that you've read that one. Please forgive the necessary redundancies.

You may have seen Everett's excellent five-part series on how to extend the modResource object. What you may not have seen, however, is much information on *why* you'd want to extend modResource.


The first question you might ask about extending modResource is, "why would I want to do that when I already have template variables?"

Template variables (TVs) are great for storing extra resource-related data and it's handy for them to have default and inherited values. You might, for example, have an e-commerce site where each product has its own resource and TV values hold the product's size, weight, and color.

What happens, though, when you need to search for resources based on their TV values? Suppose you want to display a list of all resources where the product color is red? The resources are in one table, the TV values for specific resources are in another table, and the default values of the TVs are in yet another table. You can do a monster join of the three tables, if you can figure out how, but that won't get you the inherited values or the default values.

The worst issue is that for any TV that has an inherited or default value, the actual value is not in the modTemplateVarResource, but you have to check it to know that. Then you have to pull the TV object itself, usually in a separate query, and parse its value. If the value is inherited, MODX has to walk up the resource tree until it finds a resource where the TV is set, and if it doesn't find one, it has to go back to the TV object for the default value. Bear in mind that potentially this has to be done for every TV for every resource you're retrieving. In addition there's the question of whether you want the raw value or the rendered value (another step, done once for each TV that needs it). All of these steps require a separate query to the database (and possibly complaints from the server, especially if it's a shared server).

Another big issue is sorting. There's no fast way to sort by TV values because they're split between two different tables and you have to check each one to see if you need to go to the second table. The sort has to be done after you have them all.

You could get the TV values for all the resources on the site and loop through them checking the color, but that will be quite slow and inefficient. You can get every resource on the site and examine the the relevant TVs for each one but that will be just as slow, if not slower. You can also use getResources with tvFilters, but that will be slower and even less efficient. One user reported that displaying a single page with some getResources tags and ten TVs generated over 240 queries to the database! All of those methods involve a fair amount of code and multiple queries. How much simpler (and infinitely faster) would it be if you could just do this:

$resourceDataObjects = $modx->getCollection('resourceData', array('color' => 'red'));

foreach($resourceDataObjects as $dataObject) {
    $resource = $dataObject->getOne('Resource');
    /* Do something here with the resource */

The code above will be much more efficient than looping through all the site's resources because the query will only retrieve the resources where the product's color is red. We can improve on it dramatically, though, by getting all those resources, along with the extra data fields, sorted by any field, in a single query using getCollectionGraph(). In this example, we'll sort the resources by pagetitle:

$c = $modx->newQuery('modResource');
$c->sortby('pagetitle', 'ASC');
   array('resourceData.color' => 'red'),
$resources = $modx->getCollectionGraph('modResource', '{"resourceData":{}}', $c);

After the code above has run, the $resources variable will contain an array of all resources with 'red' in the color data field. The search will be blindingly fast when compared with the other methods described above because there is only *one* query to the database, especially if there are a lot of resources on the site.


What does it mean to extend the modResource object? You might think of it as adding the equivalent of a "User Profile" for each resource. Just as the user profile contains fields that go beyond those of the modUser object, the related object for our modResource object contains fields that go beyond those of the original resource object. If you use ClassExtender's default settings (recommended) to extend the modResource object, the related object's alias will be "Data".

With the modUser object, we get the user profile with this code:

$profile = $user->getOne('Profile');

For our extended modResource object, the equivalent code looks like this:

$data = $resource->getOne('Data');

Once you have the $data object, you can get any of its fields using get() just like any other MODX object:

$data = $resource->getOne('Data');
$color = $data->get('color');

Because the $data object is an xPDO object, you can also get all the fields into an array and feed the values to a Tpl chunk with a few lines of code like this:

foreach($resources as $resource) {
    $data = $resource->getOne('Data');
    $output .= $modx->getChunk('productTpl', $data->toArray());

You might wonder why we've left out the step of loading the class or package. ClassExtender optionally registers the package in the extension_packages System Setting. That means that the package (and all its classes) will be loaded automatically on every request to the site. That means the extended class will be available on any page (including Manager pages).


One way of extending modResource is detailed here. It's quite complex and involves creating a schema, class and map files, controllers, and a number of other steps.

With ClassExtender, there's now a simpler way. Install ClassExtender and edit the example schema to meet your needs (or create a custom DB table to hold your extra fields). Then view the "Extend modResource" resource and use the form there to do all the work for you. You'll still need to update the chunk used to put your extra fields on the Create/Edit Resource panel and you may need to modify the plugin that displays and saves the fields, but it's a much simpler process than the one described above. The speed increases can be truly amazing.

See the ClassExtender Documentation for more details.

Comments (0)

Please login to comment.