ClassExtender Class Tutorial

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-premium extras with a clear conscience.

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 (sort of), it also gives you tools to update them in the database, search for your extended data, and display it. I want to acknowledge the support provided by Jean Kindem Design in the initial stages of developing this extra.

If you are upgrading from a version older than ClassExtender 2.0, see this page.

Installing ClassExtender

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.

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.

Using Template Variables (TVs) to store extra resource data has similar drawbacks.

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, by creating and autoloading all classes and tables in your schema.


Two Approaches

There are two ways to store extra data fields related to users and/or resources in custom DB tables: the traditional approach, and the ClassExtender approach. The first is described in detail in the links above.

With either method, you get to save, search, and sort the data quickly and efficiently. Either one is far superior to storing resource data in TVs, or storing user data in the `extended` field of the user profile.

The Traditional Approach

The methods described in the links above actually extend the modUser and modResource objects. The resulting objects have a new class key, but the added fields are stored in a separate, custom database table. The advantage of this method is that once you have either object, you can get its extra field data by calling $object->getOne('Data'). The downside is that your objects may not play nice with other custom objects that extend modUser or modResource, and you have to selectively use that alternate class keys in some situations.

The ClassExtender Approach

ClassExtender basically does the opposite of the traditional method. It doesn't really extend the modUser or modResource objects, though the result is the same. With ClassExtender, the object that holds the extra fields (userData or resourceData) is the primary object and no class keys are changed.

This approach makes the transition to MODX 3 much simpler because there are no custom class keys that need to be modified.

If you have the userData object you can call $object->getOne('User') to get the related modUser object, or $object->getOne('Profile') to get the related modUserProfile object. If you have the resourceData object, you can call $object->getOne('Resource') to get the related resource.

You can get the userData or resourceData objects directly given the ID of the user or resource like this:

$userDataObject = $modx->getObject('userData', array('userdata_id' => $userId));

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

(This is similar to the modUserProfile containing the user's ID in its internalKey field.)

Combo Approach

If you prefer, you can use the traditional method with ClassExtender simply by using the schemas found at the links above in the "MyExtUserSchema" and "MyExtResourceSchema" chunks. Double check to make sure that the exact spelling of classes in the schema (including all aliases) and code are consistent. Class loading is case-sensitive.

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 if you are using the ClassExtender approach described above. There's really no reason to change them. Just view the "Extend modUser" or "Extend modResource" resources, and click on the "Submit" button. This tutorial will assume that you've left the input fields and properties at their default values.

Once you submit the form, ClassExtender will call the xPDO method parseSchema() to create the class and map file and the createObjectContainer()method to create the custom database tables. It will also create modExtension package and modNamespace records for all classes, which will be loaded automatically on every page request (including Manager requests).


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).

Overview

We'll cover this in more detail below, but the basic process for extending either object is to modify the appropriate schema chunk (*all* editing of schemas should be done in the "MyExtUserSchema" chunk or the "MyExtResourceSchema" chunk).

Edit the appropriate schema chunk, 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. You shouldn't have to change any of the form fields. Submitting the form will create the class and map files, register the extension package, then create the namespaces, and the custom DB table.

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. There's an example of that in the ClassExtender resource plugin. You may wish to change the DbType of the date in your schema from DATETIME to DATE, or TIMESTAMP depending on how you want to store the date.

Table Prefix

By default, the prefix for all your custom tables is ext_. You may wish to change it to match the prefix of your MODX tables. If you do, make sure to change it in both the snippet properties on the "Create Extended ..." page(s) and in your schema.

In spite of warnings you may have seen, I've found that it causes no problems to use the ext_ prefix, and doing that will usually put the custom tables at the top when you look at the database, which is convenient during development of your custom classes and tables.

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, create or 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.

Modify them to meet your needs *before* viewing and submitting the forms on the "Extend modUser" or the "Extend modResource" resources pages. 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! To repeat: When you edit the schema, edit the schema chunk beginning with "My" in the Manager, not the file. The file will be overwritten with the content of that chunk when you submit the form.

It's recommended that you use MODX 2 style class names in your schema chunk. ClassExtender will convert them automatically, though you may want to check the schema file itself in the core/components/model/schema/ file to make sure it has converted all your classes correctly, especially if you are extending objects other than modResource and modUser.

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. Make very sure that the table is what you want and the schema is correct before using the table to store real data.

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(s), ext_user_data and ext_resource_data, and repeat the steps using your own schema.

One way to start all over if things become messed up 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 you run either of the main forms. Note that if you uninstall ClassExtender you will lose any data in the custom tables.

Important: uninstalling ClassExtender will also delete the 'My...' chunks. If you have modified them for your purposes and want to keep them, rename them before uninstalling ClassExtender.

The Examples

The snippets and chunks included with ClassExtender that work with the example resources are not finished code. They are minimally styled and will probably not do exactly what you want. You can borrow from them to meet your needs. Create your own snippets and chunks, so they won't be overwritten by upgrades to ClassExtender (except for the ones that start with "My..." — those are safe to modify).

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. 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. Any placeholders require no prefix (unlike earlier versions of ClassExtender).

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 object 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. Any placeholders require no prefix (unlike earlier versions of ClassExtender).

Important: Make sure none of your extra fields match existing fields in the modUser, modUserProfile, or modResource objects.

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

Using Your Extended Class

The packages for either extended class are registered in the extension_packages table in the database and a namespace with the appropriate paths is created in the MODX namespaces table. They will be available on every page load (including Manager pages). The addPackage() method is called automatically for each of them. This means that you should almost never need to call addPackage() or loadClass() for them. You just need to "require" the autoloader.

The Autoloader

You can include the autoloader in any plugin or snippet with this code:

require_once(MODX_CORE_PATH . 'components/classextender/model/ce_autoload.php');

For MODX 3 only, you can create a bootstrap.php file in the core/components/classextender/ directory, put the above code in the file, and remove any code that includes the autoload from the snippets. MODX will load the autoloader for you on every page load with this code in the modX class:

require $namespace['path'] . 'bootstrap.php'

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.

Extending modUser and modResource create two separate packages. The class files are placed in a directory with the same name as the package. By default, the autoloader looks for the class files in both the directories, extendeduser/, and extendedresource/. The directories it will look in are specified in the ce_autoload_directories System Setting. If you change the package name (for example, to extend some other object, or create a series of new database tables), add the new package name to the System Setting and remove any directories that don't exist.

If you will only be extending one object, you can speed up the autoloader slightly by removing the other one from the ce_autoload_directories System Setting.

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.

ClassExtender and MODX 3

ClassExtender is fully MODX 3 compatible. MODX 3 uses the package name (by default: extendeduser and extendedresource) as a namespace for the classes created by ClassExtender.

ClassExtender will automatically add the namespace to the classes and will translate a MODX 2 style schema to a MODX 3 style schema when you create the classes in MODX 3. It's recommended that you use a MODX 2 style schema even in MODX 3 to avoid having to figure out which parts of the schema require a fully qualified class name.

Important: If you have set up your classes in MODX 2 and upgraded your site to MODX 3. You should submit the forms on the "Extend modUser" and/or "Extend modResource" pages to update your class and map files. That should be all you need to do to make the transition.

Any code of your own for MODX 3 will have to include a namespace (for example extendeduser and extendedresource) and you will need either a use statement or fully qualified class names. Without a use statement MODX classes need the MODX/Revolution/ prefix and your own classes need to be prefixed by their namespace (extendeduser/ or extendedresource/.

If you are writing your own snippets, be sure to check out the example snippets (which will run in either MODX 2 or MODX 3) to see the use of the MODX and ClassExtender prefixes used to let them run in MODX 3.

Examples

There are several example 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, as well as the native MODX 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 set by MODX.

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.

Where Property Examples


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

[[!GetExtUsers?
    &where=`{"firstName:=":"Bob","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","published:=":"1"}`
    &sortby=`pagetitle`
    &sortDir=`ASC`
]]

The content of the &where property is just as it is for other xPDO-based snippets like getResources or PdoResources. 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 loggedInChunkTpl chunk used on the Update Profile page and 2) specify that chunk in the &loggedInChunk property.

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.

Because of the way the Register snippet is written and the way it handles posthooks, there is no way to introduce a CSS file in the process. As a result, you either need to include the CSS file in the page template, or use inline style information (as done in the example, though it's not valid HTML code).

There is also an optional &usernameField property in case you use a custom field for the username

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. Use the example resources and the existing Classextender snippet and plugins as a guide.

When using the ClassExtender approach described above, 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 System Settings

Setting Description Default
classextender
ce_autoload_directories Comma-separated list of directories holding class files extendeduser,extendedresource

ClassExtender Snippet Properties

Note: With the exception of the table prefix, these do not show up on the Properties tab of the snippet. The other properties should never be set there because the snippet is used for extending both modUser and modResource. You can put them in a property set, but it's usually easier just to set them with properties in the snippet tag, as in the ClassExtender example resources.

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
tablePrefix Table prefix for new DB table ext_

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
limit Number of users to retrieve null
offset Offset of first user to retrieve null
cssFile Path to CSS file to load (empty)

The &cssFile properties for all the snippets are empty by default so that you can use your site's default CSS file for the styling to speed up page loading. You can see the default CSS file path on the example pages. You may want to copy it, include it in your snippet calls, and modify it during development.

The optional offset and limit properties are for when you have a very large number of users and want to retrieve them in batches with $modx->runSnippet('getExtUsers', $properties). The $properties variable will hold an array of property keys and values. By default (without these two properties), all users that match your criteria will be retrieved.

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
cssFile Path to CSS file to load (empty)

SetUserPlaceholders Snippet Properties

Property Description Default
userId User ID empty (defaults to current user)
prefix Prefix for placeholders empty
cssFile Path to CSS file to load (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
cssFile Path to CSS file to load (empty)

UserSearchForm Snippet Properties

Property Description Default
extFormTpl Tpl chunk to use for user search form ExtUserSearchFormTpl
cssFile Path to CSS file to load (empty)

 

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