Why Extend modResource

Why and how to extend the modResource object.

By Bob Ray  |  February 7, 2023  |  8 min read
Why Extend modResource

In a 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 five-part series on how to extend the modResource object. (Revo 3 version here). At this writing, the information in the article is somewhat out of date, especially for Revo 3. If you use ClassExtender, though you won’t need to update it.

What you may not have seen, however, is much information on why you’d want to extend modResource.

Why?

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 in 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 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('resourceData');
$c->sortby('pagetitle', 'ASC');
$c->where(
   array('color' => 'white'),
);
$resources = $modx->getCollectionGraph('resourceData', '{"Resource":{]]', $c);

After the code above has run, the $resources variable will contain an array of all resourceData objects with 'white' in the color data field, along with their related resource. 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.

Say you want to display the results using a Tpl Chunk with placeholders for the fields you want to show. That code would look something like this (assuming that it’s below the code above, which sets the $resource variable):

$output = "<h3>Resources</h3>\n";

foreach ($resources as $resource) {
    $finalFields = array();
    $finalFields = $resource->toArray();
    if ($resource->Resource) {
        $fields = $resource->Resource->toArray();
        $finalFields = array_merge($finalFields, $fields);
    }
    $output .= $modx->getChunk('TplChunkName', $finalFields);
}
return $output;

What?

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 class will be “resourceData”.

Two Approaches

There are two ways to extend the modResource object. In the official MODX docs version (see link above), the primary object extends modResource and provides access to the resourceData object with the alias Data. This requires changing the class key of the custom Resource object, and makes using that object a little more complicated.

The ClassExtender Extra takes a different approach. The resourceData object is the primary object. It holds the ID of the related Resource in its resourcedata_id field. It provides access to the related Resource with the alias Resource. It doesn’t change any class keys, which can make things simpler, but you can’t call $resource->getOne('Data');. Instead, you get the resourceData object like this (where $id holds the ID of the Resource):

$resourceData = $modx->getObject('resourceData',
    array(resourcedata_id => $id));

You can then get the related Resource like this:

$resource = $resourceData->getOne('Resource');

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

$color = $resourceData->get('color');

Because the resourceData 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:


$output = "<h3>ResourceData</3>\n";

$resourceDataArray = $modx->getCollection('resourceData');
foreach($resourceDataArray as $resourceData) {
    $fields = $resourceData->toArray();
    $resource = $resourceDataArray->getOne('Resource');
    $fields = array_merge($fields, $resource->toArray();
    $output .= $modx->getChunk('productTpl', $fields);
}

Getting All Resource Fields

There’s a way to dramatically increase page-load speeds when getting all the resource fields. Instead of getting the fields of the modResource and resourceData objects with two separate queries (and usually two calls to setPlaceholders()) is to use the xPDO getCollectionGraph() method. It gets everything in one query and allows you to make a single call to setPlaceholders(). Here’s the code (lifted from ClassExtender’s GetExtResources Snippet. The Tpl Chunk should contain the HTML for showing the results, with placeholders for all the fields you want to display.

/* Resource class will need a namespace prefix
       (extendedresource/) in MODX 3 */
$resourceClass= 'resourceData';
$output = '';
$tplChunk = 'name of tpl chunk';
$c = $modx->newQuery($resourceClass);
$resources = $modx->getCollectionGraph($resourceClass, '{"Resource":{]]', $c);

$count = count($resources);

if (!$count) {
    return '<p class="ce_error">' . $modx->lexicon('ce.no_resources_found') . '';
}
foreach ($resources as $resource) {
    $fields = $resource->toArray();

    if ($resource->Resource) {
        $fields = array_merge($Resource->Resource->toArray(), $fields);
    }

    $output = $modx->getChunk($tplChunk, $fields);

    return $output;

How?

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.

I’ll discuss how to use ClassExtender to extend the modResource object in my next article.

Wrapping Up

Extending the modResource object is not for everyone. It requires some skill to make it work the way you want it to. It’s not rocket science, though, and ClassExtender will do most of the heavy lifting for you. Extending modResource can dramatically reduce your page-load times and will create possibilities for searching and sorting that would be slow and difficult using Template variables.


Bob Ray is the author of the MODX: The Official Guide and dozens of MODX Extras including QuickEmail, NewsPublisher, SiteCheck, GoRevo, Personalize, EZfaq, MyComponent and many more. His website is Bob’s Guides. It not only includes a plethora of MODX tutorials but there are some really great bread recipes there, as well.