Resolvers X - Using a Class File

Moving the code from the previous articles into a single PHP class.

By Bob Ray  |  August 26, 2025  |  3 min read
Resolvers X - Using a Class File

Using a PHP Class File

I did a lot of programming before Object-Oriented programming became popular. So it's not my style to plan out classes before I start programming. In addition, I don't recall ever seeing custom PHP classes being used in a resolver or validator that's part of a MODX transport package.

As so often happens, the code from the previous articles started out as a few lines of code, and gradually grew as I discovered I had more to do during updates of ClassExtender.

If you imagine all the code from the previous articles in this series being jammed into the "update" section of a resolver, you can understand that it might be a little confusing and difficult to maintain. It would also be almost impossible to test without duplicating the code and having to switch back and forth from the real code and the test code. It would have been wiser to put the code into a PHP class to begin with, but that's not how it worked out. As it turns out, though, moving the code into a class was quite easy. I did it in well under an hour.

With the class, you can now test every method of the class by itself with a separate unit test that's relatively easy to create and allows you to step through any part of the method in a debugger to see why tests and individual assertions are failing.

Besides being clearer, there are other advantages to using a class (I called it ClassFix). When code gets this long, there's always a danger of using the same variable name in distant parts of the code without noticing that you've done that. This is almost always disastrous, and very difficult to debug. And even a superb code editor usually won't warn you about it.

Having the code in a class file means that it's much easier to integrate it into other extras that might need the same methods. And if you need to modify any of the methods, or not call them, you can create a subclass that extends the original. As a huge bonus, you can also use the lines just below by themselves to run the code (after instantiating MODX). Even better, you can easily step through the code in a debugger — something that's extremely cumbersome (bordering on impossible) to do with all the code in the resolver.

The code necessary in the update section of the resolver is shortened dramatically. Here's what's left:

/* Fix incorrect class keys */
$file = MODX_CORE_PATH  . 'components/classextender/model/ClassFix.php';

if (file_exists($file)) {
    $modx->log(modX::LOG_LEVEL_INFO, 'Checking Class file names');
    include $file;
    $cf = new ClassFix($modx);
    $cf->init();
    $cf->process();
} else {
    $modx->log(modX::LOG_LEVEL_ERROR, 'Could not find ClassFix file');
}

In the process of creating the class file, I made a few improvements, which I'll discuss below the code.

The Class Code

You can see how ugly it would be to put all this code into the resolver. Here's the code in the ClassFix.php file

<?php

class ClassFix {
    protected string $base = '';
    protected string $prefix = '';
    protected modX $modx;
    protected string $uSearch = 'userData';
    protected string $uReplace = 'UserData';
    protected string $rSearch = 'resourceData';
    protected string $rReplace = 'ResourceData';

    protected array $searchArray = array();
    protected array $replaceArray = array();

    protected $objects = array();

    function __construct($modx) {
        $this->modx = $modx;

    }

    public function init() {

        $modx = $this->modx;
        $this->base = MODX_CORE_PATH . 'components/classextender/model/';

        $this->prefix = $modx->getVersionData()['version'] >= 3
            ? 'MODX\Revolution\\'
            : '';

        $this->searchArray = array(
            $this->uSearch,
            $this->rSearch,
        );
        $this->replaceArray = array(
            $this->uReplace,
            $this->rReplace,
        );

        $this->objects = array (
            'files' => array(
                /* User files */
                'extendeduser/metadata.mysql.php',
                'extendeduser/mysql/userdata.class.php',
                'extendeduser/mysql/userdata.map.inc.php',
                'extendeduser/userdata.class.php',
                'schema/extendeduser.mysql.schema.xml',

                /* Resource Files */
                'extendedresource/metadata.mysql.php',
                'extendedresource/mysql/resourcedata.class.php',
                'extendedresource/mysql/resourcedata.map.inc.php',
                'extendedresource/resourcedata.class.php',
                'schema/extendedresource.mysql.schema.xml',
            ),
            'resources' => array(
                'Extend modUser',
                'Extend modResource',
                'My Extend modUser',
                'My Extend modResource',
                'MyExtend modUser',
                'MyExtend modResource',
            ),
            'chunks' => array(
                'ExtUserSchema',
                'MyExtUserSchema',
                'ExtResourceSchema',
                'MyExtResourceSchema',
                'My ExtResourceSchema',
                'My ExtUserSchema',
            ),
            'snippets' => array(
                'GetExtUsers',
                'SetUserPlaceholders',
                'GetExtResources',
                'MyGetExtUsers',
                'MySetUserPlaceholders',
                'MyGetExtResources',
                'My GetExtResources',
                'My GetExtUsers',
                'My SetUserPlaceholders',
            ),
        );
    }

    public function process() {
        $this->doFiles();
        $this->doResources();
        $this->doChunks();
        $this->doSnippets();
    }

    public function doFiles() {
        $count = 0;
        foreach ($this->objects['files'] as $file) {
            $file = $this->base . $file;
            if (file_exists($file)) {
                $content = file_get_contents($file);
                if ($this->checkContent($this->searchArray, $content)) {
                    $content = str_replace($this->searchArray, $this->replaceArray, $content);
                    file_put_contents($file, $content);
                    $count++;
                }
            }
        }
        if ($count) {
            $this->output($count . ' Class names updated in files');
        }
    }

    public function doResources() {
        $count = 0;
        foreach ($this->objects['resources'] as $resource) {
            $resourceObj = $this->modx->getObject($this->prefix . 'modResource', array('pagetitle' => $resource));
            if ($resourceObj) {
                $content = $resourceObj->getContent();
                if ($this->checkContent($this->searchArray, $content)) {

                    $content = str_replace($this->searchArray, $this->replaceArray, $content);
                    $resourceObj->setContent($content);
                    $resourceObj->save();
                    $count++;
                }
            }
        }
        if ($count) {
            $this->output($count . ' Class names updated in resources');
        }
    }

    public function doChunks() {
        $count = 0;
        foreach ($this->objects['chunks'] as $chunk) {
            $chunkObj = $this->modx->getObject($this->prefix . 'modChunk', array('name' => $chunk));
            if ($chunkObj) {
                $content = $chunkObj->getContent();
                if ($this->checkContent($this->searchArray, $content)) {
                    $content = str_replace($this->searchArray, $this->replaceArray, $content);
                    $chunkObj->setContent($content);
                    $chunkObj->save();
                    $count++;
                }
            }
        }
        if ($count) {
            $this->output($count . ' Class names updated in chunks');
        }
    }

    public function doSnippets() {
        $snippetCount = 0;
        $propertyCount = 0;
        $propertySetCount = 0;

        foreach ($this->objects['snippets'] as $snippet) {
            $snippetObj = $this->modx->getObject($this->prefix . 'modSnippet', array('name' => $snippet));
            if ($snippetObj) {
                $dirty = false;
                $content = $snippetObj->getContent();
                if ($this->checkContent($this->searchArray, $content)) {
                    $content = str_replace($this->searchArray, $this->replaceArray, $content);
                    $snippetObj->setContent($content);
                    $dirty = true;
                    $snippetObj->save();
                    $snippetCount++;
                }
                $properties = $snippetObj->getProperties();
                $x = $snippetObj->get('properties');
                if (isset($properties['UserDataClass'])) {
                    if ($properties['UserDataClass'] === $this->uSearch) {
                        $properties['UserDataClass'] = $this->uReplace;
                        $snippetObj->setProperties($properties);
                        $propertyCount++;
                        $dirty = true;
                    }
                }
                if (isset($properties['ResourceDataClass'])) {
                    if ($properties['ResourceDataClass'] === $this->rSearch) {
                        $properties['ResourceDataClass'] = $this->rReplace;
                        $snippetObj->setProperties($properties);
                        $dirty = true;
                        $propertyCount++;
                    }
                }
                if ($dirty) {
                    $snippetObj->save();
                }

                /* Check property sets, if any */
                $intersects = $snippetObj->getMany('PropertySets');

                if (!empty($propertySets)) {
                    foreach ($intersects as $intersect) {
                        $dirty = false;
                        $propsetId = $intersect->get('property_set');
                        $actualPropertySet = $this->modx->getObject($this->prefix . 'modPropertySet', $propsetId);

                        $props = $actualPropertySet->get('properties');

                        if (isset($props['UserDataClass'])) {
                            if ($props['UserDataClass']['value'] === $this->uSearch) {
                                $fixedPropertySetCount++;
                                $props['UserDataClass']['value'] = $this->uReplace;
                                $actualPropertySet->set('properties', $props);
                                $dirty = true;
                                $propertySetCount++;
                            }
                        }
                        if (isset($props['ResourceDataClass'])) {

                            if ($props['ResourceDataClass']['value'] === $this->rSearch) {
                                $props['ResourceDataClass']['value'] = $this->rReplace;
                                $actualPropertySet->set('properties', $props);
                                $dirty = true;
                                $propertySetCount++;
                            }
                        }
                        if ($dirty) {
                            $actualPropertySet->save();
                        }
                    }
                }
            }
        }
        if ($snippetCount) {
            $this->output($snippetCount . ' Class names updated in snippets');
        }
        if ($propertyCount) {
            $this->output($propertyCount . ' Class names updated in snippet properties');
        }
        if ($propertySetCount) {
            $this->output($propertySetCount . ' Class names updated in property sets');
        }
    }

    /**
     * @param array $terms - array of search terms
     * @param string $content - string to search
     * @return bool - true only if content contains any $terms
     */
    protected function checkContent(array $terms, string $content)  {

        foreach ($terms as $term) {
            if (strpos($content, $term) !== false) {
                return true;
            }
        }
        return false;
    }

    /**
     * @param string $msg;
     */
    protected function output($msg) {
        if (php_sapi_name() === 'cli') {
            echo "\n" . $msg;
        } else {
            $this->modx->log(modX::LOG_LEVEL_INFO, $msg);
        }
    }
}

Improvements

The first thing you'll notice is that all the files and objects to be searched are in one big array. Having them all in one place makes it a lot easier to add or subtract from the list. Notice, too, that the search and replace terms are at the top of the file as class variables.

Second, there's an extra function at the end called output that displays the results. It's designed to work in MODX during installation of the extra, and also in CLI mode, so you can see the results when running the code in your code editor.

A final improvement is to simplify the output. During development, it was useful to have the detailed output to look at, but you wouldn't want to show all of it to the user during the installation of your extra. An alternative would have been to add a $debug class variable and show the details only when it's set to true, but it would be easy enough to add that later if any problems crop up.


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.