ClassExtender Class Tutorial

I could tell you how many hours it takes to develop a MODX extra Transport Package complete with a build script, properties, multiple MODX elements, internationalized strings, error checks, and then fully test it, but you wouldn't believe me. If you use this extra and like it, please consider donating. The suggested donation for this extra is $10.00, but any amount you want to give is fine (really). For a one-time donation of $50.00 you can use all of my non-commercial extras with a clear conscience.


PayPal

ClassExtender is designed to make it easy to extend the modUser and modResource objects in MODX. It will not only extend the objects for you, it also gives you tools update them in the database, and search for your extended objects and display them. I want to acknowledge the support provided by Kindem Designs in the initial stages of developing this extra.

Important Upgrade Information for ClassExtender 2.0+

ClassExtender 2.0 has been refactored so that it will not conflict with other extras that extend the modUser or modResource objects (e.g., ActiveDirectory). Searches should be faster and there should be no more spurious error messages in the Error Log.

Please read this carefully if you are upgrading from a version before ClassExtender 2.0. If you have already installed version 2.0 or greater, or are doing a new install, you can ignore this section.

The difference from earlier versions is that ClassExtender will no longer modify the class_key fields of the objects. In fact, when you install ClassExtender 2.0, it will revert its extUser and extResource class_key fields back to modUser and modDocument. It will not touch objects with other class keys, such as Article.

Important! If you uninstall the earlier version, you will lose any custom user or resource data currently stored in the database.

So that the upgrade will not overwrite your schema and extra fields chunks, the upgrade code will copy them to four new files with the prefix 'My':

  • ExtraUserFields will be copied to MyExtraUserFields
  • ExtUserSchema will be copied to MyExtUserSchema
  • ExtraResourceFields will be copied to MyExtraResourceFields
  • ExtResourceSchema will be copied to MyExtResourceSchema

ClassExtender will attempt to modify the MyExtUserSchema file with the new aggregate alias for the user profile. Check the aliases against those in the ExtUserSchema chunk to make sure it succeeded.

The snippets and plugins in the new versions will use the 'My' chunks, and those chunks will not be altered in future upgrades.

After Upgrading

  1. View and Submit the ExtendModUser and/or ExtendModResource Resources.
  2. If your snippet tags contain a &where property, they will need to be modified (see below).
  3. Custom snippets you have written may also need to be modified (see below).

Future upgrades will go much more smoothly. My apologies for the inconvenience.

Why Extend modUser and modResource?

As you probably know, most of the information on a user is contained in the User Profile. Often, however, you need to store information about a user that won't fit there. Using the Profile's extended fields is an option, but it's very slow and inefficient to search for or sort users based on what is in their extended fields. Extending the modUser object essentially gives you an extra user profile with as many fields as you need to store your information. Having the information there will allow you to do extremely fast searches on the extended data and sort the results.

The traditional method of extending modUser is documented here. Extending modResource is documented here. You don't need to consult those pages to use ClassExtender, but doing so may help you understand what it's doing, though ClassExtender takes a slightly different approach. It will also give you an appreciation of how much time and trouble ClassExtender is saving you.


It's *very* strongly recommended that you not change any of the values in the ClassExtender forms or snippet tags used to extend the modUser and modResource objects. There's really no reason to change them. Just click on the "Submit" button. This tutorial will assume that you've left the input fields and properties at their default values.


Once the class and map files have been created and the data is in the extra fields in the custom database table, getting the data in those extra fields is quite easy (see below).

Extending the modResource object does the same thing for that object. It provides an extra table to store the additional information you need for each resource. Having the data in the extra table is *much* faster and more efficient than storing it in Template Variables. Doing so will allow blindingly fast and efficient searching and sorting on those fields and will mean much faster page loads when displaying the fields.

Installing ClassExtender

Important! If you are upgrading from a version lower than 2.0, see the upgrade notes above!

Go to System | Package Management on the main menu in the MODX Manager and click on the "Download Extras" button. That will take you to the Revolution Repository. Put ClassExtender in the search box and press Enter. Click on the "Download" button, and once the package is downloaded, click on the "Back to Package Manager" button. That should bring you back to your Package Management grid. Click on the "Install" button next to ClassExtender in the grid. The ClassExtender package should now be installed.

Overview

We'll cover this in more detail below, but the basic process for extending either object is to modify the appropriate schema chunk to meet your needs, then view either the "Extend modUser" resource or the "Extend modResource" resource. That will present you with a form that, when submitted, will do all the work for you. It will create the class and map files, and register the extension package. Once that's done, all that's left is to modify the chunk used to display the extra fields in the Create/Edit Resource or User panel in the Manager. The appropriate plugin will be enabled automatically. If any of your fields require special handling (e.g., a date field that should be set to today's date), you'll also have to modify the plugin to handle that.

Object Schema

In order to add the extra fields to your extended object, you need to create a specification for the fields. Do this by modifying one of the existing schema chunks. There are two example schema chunks in the package: ExtUserSchema and ExtResourceSchema. You should never modify them. Instead, modify the MyExtUserSchema and MyExtResourceSchema chunks. These are the chunks that are actually used by ClassExtender, and they will not be modified in future upgrades.

You can modify them to meet your needs before viewing the "Extend modUser" or the "Extend modResource" resources). ClassExtender uses a schema file to do its work, but the first thing it does is to dump the appropriate chunk's content to the file.

Important! When you edit the schema, edit the chunk in the Manager, not the file. The file will be updated when you submit the form.

The resourcedata_id or userdata_id field will contain the ID of the user or resource that table row is related to.

If you don't like the results, just drop the ext_user_data and/or ext_resource_data table in the DB, edit your schema chunk, and submit the form again. New class and map files and a new table will be created.

A Recommendation

This is entirely up to you, but I would recommend performing the entire process using the examples provided with ClassExtender. You'll see how everything works and can verify that ClassExtender operates as it should before trying to create your own extended classes. Once the process is completed, you can drop the existing database table and repeat the steps using your own schema.

One easy way to start all over is to uninstall and re-install ClassExtender. That will remove all traces of ClassExtender. ClassExtender will generate completely new class and map files every time it runs so the example ones will be overwritten with yours.

Note that uninstalling will delete the 'My...' chunks created during the install. If you have modified them for your purposes and want to keep them, rename them before uninstalling ClassExtender.

The Examples

The files and objects included with ClassExtender serve as examples that you can modify to meet your needs.

The User examples are something I did for a client. The client needed extra fields for firstName, lastName, title, registrationDate, and company. The registrationDate field is not shown on the form. It's set to today's date only if the field in the database is empty. The MyExtraUserFields chunk contains the HTML to show the fields on the Create/Edit User panel. It will not be modified during future upgrades of ClassExtender.

The ExtraUserFields plugin displays those fields on the Create/Edit user panel and saves the values to the custom table in the database. The plugin will be enabled automatically when you extend the modUser object.

The Resource examples are for an imaginary site where each resource represents an animal (at, say, a shelter, pet shop, or veterinary clinic). There are extra fields for the name, color, breed, and age of the animal. The MyExtraResourceFields chunk contains the HTML to show the fields on the Create/Edit Resource panel. It will not be modified during future upgrades of ClassExtender.

The ExtraResourceFields plugin displays the fields on the Create/Edit Resource panel and saves the values to the custom table in the database. The plugin will be enabled automatically when you extend the modResource object.

Extending modUser

Here are the basic steps necessary to extend the modUser object. You shouldn't need to modify any fields in the form:

  1. Modify the MyExtUserSchema chunk to meet your needs. Usually, all you really need to change are the "fields" of the userData object in the schema.
  2. View the "Extend modUser" resource.
  3. Click on Submit.
  4. Modify the MyExtraUserFields chunk to display your extra fields. Be sure to use the names specified for the fields in your schema.
  5. If necessary, modify the ExtraUserFields plugin to deal with fields that require special handling.

Once you've completed the steps above, you should see your extra fields on the Create/Edit User panel and they should be saved to the ext_user_data table in the database.

Extending modResource

Here are the basic steps necessary to extend the modResource object. You shouldn't need to modify any fields in the form:

  1. Modify the MyExtResourceSchema chunk to meet your needs. Usually all you need to modify are the "fields" of the resourceData objext in the schema.
  2. View the "Extend modResource" resource.
  3. Click on Submit.
  4. Modify the MyExtraResourceFields chunk to display your extra fields. Be sure to use the names specified for the fields in your schema.
  5. If necessary, modify the ExtraResourceFields plugin to deal with fields that require special handling.

Once you've completed the steps above, you should see your extra fields on the Create/Edit Resource panel and they should be saved to the ext_resource_data table in the database.

Using Your Extended Class

Because the packages for either extended class are registered in the extension_packages System Setting, they will be available on every page load (including Manager pages). This means there is almost never a need to call addPackage() or loadClass() for them.

Once you have run ClassExtender for an extended class, you can get the user or resource data from the extended table with xPDO anywhere in MODX (see below). The only situation where you would need to load the class or package explicitly might be for part of a CMP (e.g., a connector or processor) or code to be run outside of MODX where there is no request. In those cases, you should call addPackage(), loadClass(), or $modx->getService() as appropriate.

Troubleshooting

If your extra fields fail to show up in the Create/Edit User or Resource forms in the Manager, make sure the appropriate plugin in the ClassExtender category is enabled.

If the fields are not saved to the database, make sure the field names specified in your MyExtraUserFields or MyExtraResourceFields match those specified in your schema chunk.

Utilities

There are several utility snippets included with ClassExtender. They may do what you want, or you may need to modify them.

The simplest of the utilities are SetUserPlaceholders and SetResourcePlaceholders. These simply set placeholders for the extra fields. Both take a property (&userId or &resourceId that lets you select the user or resource to get the fields for. If those are omitted, the current user or current resource is assumed.

SetUserPlaceholders will set placeholders for all fields of the User object (except sensitive fields like password), the User Profile object, and the extended fields from ClassExtender. It will not set placeholders for the traditional user profile extended fields, since you should replace those with fields in your custom table.

SetResourcePlaceholders will set placeholders for all resource fields and all extended fields from ClassExtender. It will not set placeholders for TVs. Important! If you are using SetResourcePlaceholders to show a user other than the current user, be sure to use a prefix for the placeholders so they won't conflict with the placeholders for the current page.

For example, to show the extra field values for the current resource (using the included example fields), you could do this:

[[!SetResourcePlaceholders]]

<p>Pagetitle: [[+pagetitle]]</p>
<p>Name:  [[+name]]</p>
<p>Color: [[+color]]</p>
<p>Breed: [[+breed]]</p>
<p>Age:   [[+age]]</p>

SetUserPlaceholders works the same way:

    [[!SetUserPlaceholders]]

    <p>Username: [[+username]]</p>
    <p>Email: [[+email]]</p>
    <p>Name: [[+name]]</p>
    <p>Color: [[+color]]</p>
    <p>Breed: [[+breed]]</p>
    <p>Age: [[+age]]</p>

The package also includes two snippets: GetExtUsers and GetExtResources. These operate a little like getResources, only without some of the bells and whistles. They use Tpl chunks to display sorted, aggregated Users or Resources based on some search criteria. You can specify the Tpl chunks, sorting, and selection criteria in the properties of the snippet tag.

As of version 2.0 of ClassExtender, the usage of the &where property has changed.

In the &where property for Users, the custom fields should not have a prefix (e.g., firstName), the user fields should be prefixed with User (e.g. User.username), and the profile fields should be prefixed with Profile (e.g., Profile.email).

See this page to determine which fields are in the modUser object, and which fields are in the modUserProfile object.

For Resources, the custom fields should have no prefix and the resource fields should be prefixed with Resource..

See this page for a list of the modResource object fields.

Also, see the examples below.

Where Property Examples


/* Get all active users with the first name 'Bob' in the
    sorted by last name custom fields: */

[[!GetExtUsers?
    &where=`{"firstName:=":"Bob","User.active:="1"}`
    &sortby=`lastName`
    &sortDir=`ASC`
]]


/* Get all Users with the first name Bob or Susan,
   sorted by last name: */

[[!GetExtUsers?
    &where=`{"firstName:=":"Bob","OR:firstName:=":"Susan"}`
    &sortby=`lastName`
    &sortDir=`ASC`
]]

/* Get all published resources where the breed is
   'poodle', sorted by pagetitle */

[[!GetExtResources?
    &where=`{"breed:=":"poodle","Resource.published:=":"1"}`
    &sortby=`pagetitle`
    &sortDir=`ASC`
]]

The content of the &where property is just as it is for other xPDO-based snippets like getResources. There are some examples here.

With the GetExtUsers snippet, you can use the username, active, and all fields in the Profile and Data tables as placeholders in the Tpl chunks.

With the GetExtResources snippet, you can use all standard resource fields and all fields in the Data tables as placeholders in the Tpl chunks.

The GetExtResources snippet will not select or display Template Variables (TVs). Adding generic TV capability would have made the snippet quite slow and would defeat the purpose of moving TV data into the extra fields. If you absolutely need TVs, it will be much more efficient to modify the GetExtResources snippet code to display the particular TVs you need, and searching or sorting by TVs is a bad idea to begin with.

Similarly, the GetExtUsers snippet will not handle the traditional user extended fields, since performance will be much better if you make them custom Data fields using ClassExtender.

More Utility Snippets

There are three other utility snippets: ExtUserUpdateProfile, ExtUserRegisterPosthook, and UserSearchForm. The first is used to extend the UpdateProfile snippet (part of the Login package) to display and update the extra user fields. In simple cases, you should be able to just 1) add the custom fields to the form on the Update Profile page and 2) add a tag for the snippet on the page (above the form), like this:

[[!ExtUserUpdateProfile]]
[[!UpdateProfile]]

Be sure the ExtUserUpdateProfile tag is above the UpdateProfile tag and that both snippets are called uncached (with the exclamation point). If any form fields require special handling (e.g., date fields), you'll have to modify the snippet to deal with them. When you are testing the Update Profile form, you may see some odd behavior if either your browser, or a password manager like LastPass, is trying to be helpful by filling in the forms for you. This generally won't affect real users.

The second snippet (ExtUserRegisterPosthook) is very similar. It saves the custom fields to the database when a user registers. Add your custom fields to the registration form and modify the Register snippet tag as follows:

[[!Register?
    &submitVar=`loginRegisterBtn`
    // ...
    &useExtended=`0`
    &postHooks=`ExtUserRegisterPosthook`
]]

The third snippet (UserSearchForm) serves as an example of how to search for users using the custom fields. You will have to modify it to meet your needs.

Uninstalling ClassExtender

Important: Even though ClassExtender only needs to be run once to create the class and map files for the extended classes, DO NOT uninstall it unless you will not be using any extended classes. The class and map files, all chunks (including the 'My..." chunks), the namespace, and any database tables will be removed during the uninstall.

When you uninstall ClassExtender, you may see some error messages. This is normal. There may also be some spurious error messages in the MODX Error Log.

During the uninstall, ClassExtender will remove its various components and namespace, de-register the extension package(s), and drop any tables it created. When the uninstall is finished, there should be no trace of ClassExtender left on your site. Note that any data stored in the ClassExtender tables will be lost.

Your Own Code

You may find it necessary to create your own code to deal with the custom tables created by ClassExtender. You may already have done so. ClassExtender 2.0 takes a new approach in order to make ClassExtender play nice with other extras that modify modUser or modResource, so any code you've created will probably have to change.

ClassExtender 2.0 and later no longer modifies the class_key field of the objects. It will remain as modUser or modDocument. This means that your queries cannot be based on extUser or extResource. Instead, you need to base your query on the custom object containing the extended fields: userData or resourceData.

In Version 1.x of ClassExtender, the custom fields object was a related object of the extUser or extResource object. As of Version 2.0, this is reversed. For users, the modUser and modUserProfile objects are related objects of the userData object (with the aliases User and Profile. Similarly, the modResource object is now a related object of the resourceData object (with the alias resourceData).

This makes for faster searching, fewer spurious error messages, and no more interference with other Extras that extend the modUser or modResource objects. One down side is that when you delete a Resource or User, the custom data row for that object is not automatically deleted. The ExtraUserFields and ExtraResourceFields plugins have been modified to do the deletion.

If you need to get a related object, you now have to do it the other way around. This code will no longer work because the modUser object is not aware of the extra fields:

$user = $modx->getObject('extUser', 23);
$data = $user->getOne('Data');

Instead, you do this:

    $data = $modx->getObject('userData', array('userdata_id' => 23));
    $user = $data->getOne('User');
    $profile = $data->getOne('Profile');

Similarly, with Resources, you would now do this:

    $data = $modx->getObject('resourceData', array('resourcedata_id' => 23));
    $resource = $data->getOne('Resource');

A much faster and more efficient method, however is to get all the fields in a single query with $modx->getObjectGraph() or $modx->getCollectionGraph(). The first one gets a single object and its related objects. The second gets a collection of objects and their related fields.

You can get all the combined user fields with code like one of the following lines (where $c is an xPDO query object specifying the criteria):

$c = $modx->newQuery('userData');
$c->where(array('User.username' => 'BobRay'));
$user = $modx->getObjectGraph('userData', '{"Profile":{},"User":{}}', $c);
$users = $modx->getCollectionGraph('userData', '{"Profile":{},"User":{}}', $c);

You can get all the combined resource fields with code like one of the following lines (where $c is an xPDO query object specifying the criteria):

$c = $modx->newQuery('resourceData');
$c->where(array('Resource.pagetitle' => 'Home'));
$resource = $modx->getObjectGraph('resourceData', '{"Resource":{}}', $c);
$resources = $modx->getCollectionGraph('resourceData', '{"Resource":{}}', $c);

You can still search and/or sort on the fields in any of the related objects, but the prefixes to use in the &where property have changed. See the examples above.

See the code of the GetExtUsers or GetExtResources snippets for more details.

If you have a User object and would like to get its custom extended fields, do this:

$data = $modx->getObject('userData', array('userdata_id' => $user->get('id'));
$fields = $data->toArray();

If you have a Resource object and would like to get its custom extended fields, do this:

$data = $modx->getObject('resourceData', array('resourcedata_id' => $resource->get('id'));
$fields = $data->toArray();

When you do a search with GetExtUsers or GetExtResources, only Users or Resources that have been saved with the ExtraUserFields or ExtraResourceFields plugins enabled will be found, since they are the only objects that will exist in the custom tables.

Creating Objects

Although you can't base an xPDO query on extUser or extResource, you can create either object with $modx->newObject() and attach related objects to it. Be sure to set the class_key field to modUser or modDocument before saving the object.

This works fine:

        
/* Create the extUser object */        
$user = $modx->newObject('extUser');
$user->set('class_key', 'modUser'); /* Important! */
$user->set('username', 'newExtUser');
$user->set('active', '1');

/* Create the userData Object */        
$data = $modx->newObject('userData');
$data->set('firstName', 'Bob');
$data->set('lastName', 'Ray');        

/* Create the User Profile */
$profile = $modx->newObject('modUserProfile');

$profile->set('email', 'bobray@softville.com');
$profile->Set('fullname', 'Bob Ray');

/* Connect the two related objects */        
$user->addOne($data);        
$user->addOne($profile);

/* Notice that we only have to save the $user object
   here. The two related objects will also be saved */
$user->save();

This works fine too:

    /* Create the extResource object */
    $resource = $modx->newObject('extResource');
    $resource->set('class_key', 'modDocument'); /* Important!! */
    $resource->set('pagetitle', 'newExtResource');
    $resource->set('published', '1');
    $resource->set('alias', 'new-ext-resource');
    $resource->set('template', $modx->getOption('default_template');

    /* Create the resourceData Object */
    $data = $modx->newObject('resourceData');
    $data->set('name', 'Penny');
    $data->set('breed', 'Labrador');
    $data->set('age', '12');

    /* Connect the related data object */
    $resource->addOne($data);

    /* Notice that we only have to save the $resource object
    here. The data object will also be saved */
    $resource->save();

Note that the saved user or resource object is just a regular modUser or modDocument object, since we have set its class_key field to one of those.

A Word of Warning

In order to get the most out of the extended objects created by ClassExtender, you may need some knowledge of PHP in order to modify the code to do what you want. You'll also need to either modify the XML schema or create your own PHP table to hold the data, though neither of these is particularly difficult. Standard MODX extras like getResources won't know about your extra fields, so they'll need to be modified to include them, though they'll still work fine in cases where you don't need to display the extra fields.

ClassExtender Snippet Properties

Note: With the exception of the table prefix, these do not show up on the Properties tab of the snippet. They other properties should never be set there because the snippet is used for extending both modUser and modResource.

Property Description Default
package Name of the package being created (e.g., extendeduser, extendedresource) empty
schemaTpl Name of the Tpl chunk to use for the schema empty
class Name of class being created (e.g., extUser, extResource) empty
parentObject Class that the object being created extends (e.g., modUser, modResource) empty
tablePrefix Table prefix for new DB table ext_
tableName Name for DB table without the prefix (e.g., user_data, resource_data) empty

GetExtUsers Snippet Properties

Property Description Default
extUserInnerTpl Name of inner Tpl chunk to use for user listing extUserInnerTpl
extUserOuterTpl Name of outer Tpl chunk to use for user listing extUserOuterTpl
extUserRowTpl Name of row Tpl chunk to use for user listing -- displays individual user data extUserRowTpl
userDataClass Class for user object userData
where JSON string containing query criteria empty
sortby Field to sort by (e.g., username, Profile.fullname, Data.lastname) username
sortdir Direction to sort in (ASC, DESC) ASC

GetExtResources Snippet Properties

Property Description Default
extResourceInnerTpl Name of inner Tpl chunk ExtResourceInnerTpl
extResourceOuterTpl Name of outer Tpl chunk ExtResourceOuterTpl
extResourceRowTpl Name of row Tpl chunk ExtResourceRowTpl
resourceDataClass Name of extended resource class resourceData
sortby Field to sort by (e.g., pagetitle, Data.somefield) pagetitle
sortdir Direction to sort in (ASC, DESC) ASC
where JSON string with search criteria empty

SetUserPlaceholders Snippet Properties

Property Description Default
userId User ID empty (defaults to current user)
prefix Prefix for placeholders empty

SetResourcePlaceholders Snippet Properties

Property Description Default
resourceId ID of resource to set placeholders from empty (defaults to current resource)
prefix Prefix for placeholders empty

UserSearchForm Snippet Properties

Property Description Default
extFormTpl Tpl chunk to use for user search form ExtUserSearchFormTpl

 

My book, MODX: The Official Guide - Digital Edition is now available here. The paper version of the book is available from Amazon.

If you have the book and would like to download the code, you can find it here.

If you have the book and would like to see the updates and corrections page, you can find it here.

MODX: The Official Guide is 772 pages long and goes far beyond this web site in explaining beginning and advanced MODX techniques. It includes detailed information on:

  • Installing MODX
  • How MODX Works
  • Working with MODX resources and Elements
  • Using Git with MODX
  • Using common MODX add-on components like SPForm, Login, getResources, and FormIt
  • MODX security Permissions
  • Customizing the MODX Manager
  • Using Form Customization
  • Creating Transport Packages
  • MODX and xPDO object methods
  • MODX System Events
  • Using PHP with MODX

Go here for more information about the book.

Thank you for visiting BobsGuides.com

  —  Bob Ray