Using xPDO for Custom Queries in MODX Revolution: A Step‑by‑Step Guide
Learn how to build and run a custom database query with MODX Revolution’s xPDO ORM. This guide covers prerequisites, building the query, verification steps, and recovery options so you can safely fetch data without raw SQL.
01 Jul 2025, 02:07 UTC

Why Use xPDO Instead of Raw SQL?
MODX Revolution ships with xPDO, a lightweight ORM that maps database tables to PHP classes. Using xPDO keeps your code portable across database engines, automatically handles escaping, and integrates with MODX’s caching and event system. Raw SQL bypasses these benefits and can lead to duplication, security gaps, and maintenance headaches.
Desired Outcome
Write a snippet that retrieves the five most recent published resources, returns them as hydrated modResource objects, and outputs the titles as a comma‑separated string.
Prerequisites
- MODX Revolution 2.x or 3.x installed and running.
- Access to the Manager to create snippets.
- PHP error reporting enabled during development (e.g.,
ini_set('display_errors',1); error_reporting(E_ALL);). - For non‑core tables, an xPDO model generated via
php generate_model.phpand loaded with$modx->addPackage(). - Sufficient memory to hold the result set; avoid
limit()when querying large collections.
Focused Procedure
Initialize the Query
In a snippet, start by creating a new query object for the target class. For core tables, the class name is the same as the table prefix plus the capitalized table name. Example for resources:
// Inside a snippet /* @var $modx modX */ $query = $modx->newQuery('modResource');Add Conditions
Use the
where()method to filter. Operators are expressed as array keys with a colon suffix. For published resources:$query->where(array( 'published' => 1, 'deleted' => 0, 'publishedon:>' => date('Y-m-d H:i:s', strtotime('-1 month')) ));Order and Limit
Sort by
publishedondescending and limit to five results:$query->sortby('publishedon', 'DESC'); $query->limit(5);Execute and Hydrate
Run the query with
getCollection()to receive hydrated objects. If you need raw arrays, chainselect()and calltoArray()on each object.$resources = $modx->getCollection('modResource', $query);Output the Result
Iterate over the collection and build a string of titles:
$titles = array(); foreach ($resources as $resource) { $titles[] = $resource->get('pagetitle'); } echo implode(", ", $titles);Optional: Inspect Generated SQL
For debugging, dump the SQL that xPDO will run:
echo $query->toSQL();
Expected Checks
- Result Type:
$resourcesshould be an array ofmodResourceobjects. Verify withis_object($resources[0])orget_class($resources[0]). - Hydration: Calling
$resource->get('pagetitle')should return a string, not null. - SQL Accuracy: Compare
$query->toSQL()against a manual query in your database client to ensure conditions and ordering match expectations. - Cache Impact: After first run, the snippet’s result may be cached by MODX. Clear the cache if you modify the snippet logic.
Recovery Options
- Snippet Failures: If the snippet throws an exception or halts page rendering, disable it from the Manager (Resources → Snippets → Delete or Disable). Then clear the MODX cache via Manage → Clear Cache or by deleting
core/cache/*manually. - Database Errors: Check
core/cache/logs/error.logfor PDO or xPDO errors. The log will contain stack traces pointing to the problematic line. - Memory Exhaustion: If the snippet fails with “Allowed memory size exhausted”, add a
limit()clause or select only necessary fields withselect('id','pagetitle').
Custom Table Example
When querying a non‑core table, you must first generate an xPDO model. Assume you have a table my_custom_table with columns id, name, and createdon. Steps:
- Generate the model:
php generate_model.php --source=core/components/mycomponent/model/schema/mycomponent.mysql.schema.xml --output=core/components/mycomponent/model/ --namespace=mycomponent - Load the package in your snippet:
$modx->addPackage('mycomponent', core_path . 'components/mycomponent/model/'); - Query the table:
$query = $modx->newQuery('mycomponent\\MyCustomTable'); $query->where(array('createdon:>' => date('Y-m-d H:i:s', strtotime('-7 days')))); $results = $modx->getCollection('mycomponent\\MyCustomTable', $query);
Limitations & Version Notes
- MODX Evolution (1.x) uses a different DBAPI; the guide applies only to Revolution.
- In MODX 3, core classes are namespaced (
MODX\\Revolution\\modResource). Snippet code may need to reference the full class name or useuse MODX\\Revolution\\modResource;for clarity. - The
addPackage()path syntax changed between 2.x and 3.x; always verify againstcore_pathconstant. - Large collections without
limit()can exhaust memory. Use pagination orselect()to narrow the result set.
Practical Verification Checklist
- Run the snippet and confirm the output string matches the titles of the newest resources in the Manager.
- Open the generated SQL (
$query->toSQL()) and run it in a database client to ensure it returns the same rows. - Check
core/cache/logs/error.logafter execution for any PHP or xPDO errors. - Disable the snippet, clear cache, and re‑enable to ensure no stale data remains.
Conclusion
By leveraging xPDO’s fluent query builder, you can write maintainable, database‑agnostic code that integrates seamlessly with MODX’s caching and event systems. The key is to keep queries concise, test the generated SQL, and handle errors gracefully using the built‑in recovery steps outlined above.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.