Understanding MODX Extended Fields and JSON

You may have used the extended fields in MODX user profiles in the MODX Manager, where MODX provides a convenient interface for them. To use extended fields in code, however, it helps to understand a little bit about how they are stored.

Most of the fields of MODX objects like resources, users, chunks, snippets, etc. are stored as either integers or strings of text. When you call an object’s get() method in code, xPDO checks the dbType of the object and returns something appropriate. In an earlier blog post, we saw how calling get() on a date field of a resource such as createdon or publishedon returns a human-readable time/date string even though the value stored in the database is an integer representing a unix timestamp.

Sometimes, though, the field in the database needs to store more information than you could put in a simple string or integer. JavaScript Object Notation (JSON) is just the tool for this.

JSON Strings

JSON is a way of storing complex data in the form of a string. Note that you don’t really have to understand JSON to handle extended fields. Because of the way MODX handles them, the JSON will never exist in your code. The explanation below is partly to satisfy the curious, but it’s also good to have a little understanding of JSON because it can be used elsewhere in MODX to specify criteria for queries in various MODX extras like getResources.

A JSON string can hold several types of data, but the one we’re interested in here is usually a simple JSON object, which is a series of key/value pairs in this form:

{"favoriteColor":"blue","favoriteNumber":"12"}

The JSON is easier to understand if we format it like this:

{
   "favoriteColor":"blue",
   "favoriteNumber":"12"
}

If these were extended fields in a user profile, this user’s favorite color would be blue and his or her favorite number would be 12. Notice that the curly braces enclose the entire list (technically, a JSON object), the key/value pairs are separated by a colon, and the pairs are separated by a comma (with no comma at the end of the list).

The extended field can also hold nested sets of values. This is not often done, but suppose that in addition to the favorites listed above we also wanted to store the names and ages of the user’s children. On the user’s “Extended Fields” tab in the Manager, you could create a container called “children” and add an attribute for each child (by right-clicking on the “children” container) with the child’s name as a key and the child’s age as the value. That would give us a JSON string that looked like this:

{"favoriteColor":"blue","favoriteNumber":"12","children":{"Bobby":"3","Susie":"5"}}

or reformatted to show the structure:

{
    "favoriteColor":"blue",
    "favoriteNumber":"12",
    "children":
        {
            "Bobby":"3",
            "Susie":"5"
        }
}

Displayed in this form, you can see that children is a key just like favoriteColor and favoriteNumber, but it’s value is another JSON object — a list with two key/value pairs of its own.

Extended Fields in Code

If you look at the fields of the modUserProfile object here, you’ll see that the type of the extended field is listed as json. When you use this code:

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

MODX (xPDO actually) sees that the type of the extended field is json and automatically calls $modx->fromJSON on it before returning the result. What comes back is a regular PHP associative array. In our first example, that would look like this if you called print_r() on the $extended variable:

Array
(
    [favoriteColor] => blue
    [favoriteNumber] => 12
)

The array from our second example would look like this:

Array
(
    [favoriteColor] => blue
    [favoriteNumber] => 12
    [children] => Array
        (
            [Bobby] => 3
            [Susie] => 5
        )
)

When you save the extended field with $profile->set('extended', $someArray) xPDO does the conversion in reverse by calling $modx->toJSON() on the value before saving it to the database. This means that you never have to deal with the JSON strings yourself when getting and setting extended fields in code.

In theory, the extended field can be a nested set of arrays with as many levels as you want. Each child could have the child’s name as a key with a value that was an array containing the child’s age, birthday, height, and weight. In practice, deeply nested arrays can be fairly challenging to work with.

Setting Placeholders

Setting MODX placeholders from the extended array is a common task. The easiest way to do that is to use the Profile snippet, which is part of the Login package. If the Login package is installed, all you need to do is put this tag at the top of your page and the username, id, and all profile fields, including any extended fields will be set as placeholders. For security, the Profile snippet does not set any of the password fields as placeholders.

[[!Profile]]

If you want to set the placeholders for some other user, you can add the username or user ID to the snippet tag. You can also add an optional prefix that will be prepended to all placeholders:

[[!Profile? &user=`BobRay` &prefix=`profile.`]]

If you just want the extended fields set as placeholders, without setting the other fields of the user profile, you’ll have to do it yourself, though the code is very simple:

[[!SetProfilePlaceholders]]

 

/* SetProfilePlaceholders snippet */
$usr = $modx->user;
/* Optional code to get some other user:
   $usr = $modx->getObject('modUser',
       array('name' => 'BobRay'));
*/
$profile = $usr->getOne('Profile');
if ($profile) {
    $extended = $profile->get('extended');
    $modx->toPlaceholders($extended, '');
} else {
    $modx->log(modX::LOG_LEVEL_ERROR,
        'Could not find profile for user: ' .
        $usr->get('username'));
}

The second argument to toPlaceholders() is an optional prefix to be prepended to all placeholders. Use it if you think there’s a chance that the placeholders will conflict with other placeholders on the page.

Changing Extended Fields

Sometimes, you want to change the value of an extended field in code, maybe as a result of the user filling out a form with their preferences. The trick is to remember that you should always get the extended array first before making any changes to it. If you set an extended field value without doing this, you’ll wipe out any other extended fields when you save the profile. Other extended fields might have been set by an extra without you knowing it, so it’s always a good idea to get the fields first before changing any of them.

Say, for example, that you want to change the user’s favorite color from blue to red. The code to do that would look like this:

$usr = $modx->user;
/* see code above for getting another user */
$profile = $user->getOne('Profile');
if ($profile) {
    $extended = $profile->get('extended');
    $extended['favoriteColor'] = 'red';
    $profile->set('extended', $extended);
    $profile->save();
} else {
    $modx->log(modX::LOG_LEVEL_ERROR,
        'Could not find profile for user: ' .
        $usr->get('username'));
}

For nested extended arrays, you can do the same thing by setting the appropriate field of the array. For our second example above, we could change Susie’s age by replacing line six above with this:

    $extended['children']['Susie'] = '6';

Adding Extended Fields

After creating extended fields in the Manager, you might think that there’s more to them than there really is. The JSON string stored in the extended field of the user profile is all there is to them. Because of xPDO’s slick way of handling them, all you need to do is to add one or more new members to the array before calling set() and any fields you’ve added will be extended fields just like any of the others.

For example we could turn our first example above into the second example just by doing this:

$usr = $modx->user;
/* see code above for getting another user */
$profile = $user->getOne('Profile');
if ($profile) {
    $extended = $profile->get('extended');
    $extended['children'] = array(
        'Bobby' => '3',
        'Susie' => '5'
    );
    $profile->set('extended', $extended);
    $profile->save();
} else {
    $modx->log(modX::LOG_LEVEL_ERROR,
        'Could not find profile for user: ' .
        $usr->get('username'));
}

Bulk Adding Extended Fields

Suppose you want to add the extended fields from our first example above to all user profiles. It’s a royal pain to load each user and create them manually in the Manager. It’s much easier to write simple utility snippet to do it for you. We’ll leave the values blank, but you can set them if you like. You can put a tag for the snippet on a temporary resource and view the resource to run the snippet:

[[!AddExtendedFields]]

 

/* AddExtendedFields snippet */
$users = $modx->getCollection('modUser');
foreach($users as $user) {
    $profile = $user->getOne('Profile');
    if ($profile) {
        $extended = $profile->get('extended');
        $extended['favoriteColor'] = '';
        $extended['favoriteNumber'] = '';
        $profile->set('extended', $extended);
        if ($profile->save()) {
            echo '<br/>Processed user: ' .
                $user->get('username');
        } else {
            echo '<br />Error saving user: ' .
                $user->get('username');
        }
    } else {
        echo '<br />Could not find profile for user: ' .
            $usr->get('username');
    }
    echo '<br />Finished!';
}

A Word of Warning

When setting placeholders from the user profile, it’s tempting to do something like this:

/* Don't do this! */
$profile = $modx->user->getOne('Profile');
$fields = $profile->toArray();
foreach($fields as $key => $value) {
    $modx->setPlaceholder($key, $value);
}

This seems safe enough, but it’s not. Not only will the extended placeholders not be set, if E_NOTICE errors are enabled, you’ll get an “array to string conversion” error. This happens because the extended field is itself an array. The setPlaceholder() method expects a string, not an array, as its second argument. When the loop gets to the extended field, things will go sideways. The toPlaceholders() method handles this properly, but setPlaceholder() won’t.


For more information on how to use MODX to create a web site, see my web site Bob’s Guides, or better yet, buy my book: MODX: The Official Guide.

Looking for quality MODX Web Hosting? Look no further than Arvixe Web Hosting!

Tags: , , , , , , | Posted under MODX, MODX | RSS 2.0

Author Spotlight

Bob Ray

Bob Ray

I am the author of MODX: The Official Guide and over 30 MODX add-on components. I host Bob's Guides, a source of valuable information for MODX users, and I've been very active in the MODX Forums with over 14,000 posts.

Leave a Reply

Your email address will not be published. Required fields are marked *


2 + = 10

You may use these HTML tags and attributes: <a href="" title="" rel=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>