1. Overview
      1. What It Is
      2. Why I Would Want to Use It
      3. Known Issues
    2. Getting Started
      1. Fundamentals
        1. Design Patterns
        2. xPDO, the Class
          1. As Object and Relational Mapper
          2. As PDO Wrapper
          3. As Service Layer
          4. The xPDO Constructor
            1. Hydrating Fields
      2. Introduction to the xPDO Object Model
        1. Understanding xPDOCriteria and xPDOQuery
      3. Creating a Model With xPDO
        1. Defining a Schema
          1. Defining Relationships
          2. Defining the Database and Tables
            1. Upgrading Models to Schema Version 1.1
          3. More Examples of xPDO XML Schema Files
          4. Validation Rules in your Schema
        2. Generating the Model Code
          1. Domain Classes
          2. O-R Maps
          3. Table Classes
      4. Using Your xPDO Model
        1. Creating Objects
        2. Database Connections and xPDO
        3. Loading Packages
        4. Removing Objects
          1. removeCollection
        5. Retrieving Objects
          1. getCollectionGraph
        6. Setting Object Fields
        7. Working with Related Objects
    3. Extending Your xPDO Model
      1. Overriding Derived Behavior
        1. Using Custom Object Loaders
      2. Working With Transient Object Classes
    4. Advanced Features
      1. Caching
        1. Caching Tutorial - Basic Snippets
        2. Caching Tutorial - Lifetimes
      2. Object Validation
        1. xPDOForeignKeyConstraint
        2. xPDOMaxLengthValidationRule
        3. xPDOMaxValueValidationRule
        4. xPDOMinLengthValidationRule
        5. xPDOMinValueValidationRule
        6. xPDOObjectExistsValidationRule
    5. xPDO Development
      1. Testing
      2. Building
      3. Documentation and Tutorials
    6. Class Reference
      1. xPDO
        1. xPDO.addPackage
        2. xPDO.beginTransaction
        3. xPDO.commit
        4. xPDO.connect
        5. xPDO.fromJSON
        6. xPDO.getCollection
        7. xPDO.getCollectionGraph
        8. xPDO.getCount
        9. xPDO.getDebug
        10. xPDO.getFields
        11. xPDO.getIterator
        12. xPDO.getManager
        13. xPDO.getObject
        14. xPDO.getObjectGraph
        15. xPDO.getOption
        16. xPDO.getTableName
        17. xPDO.loadClass
        18. xPDO.log
        19. xPDO.newObject
        20. xPDO.newQuery
        21. xPDO.query
        22. xPDO.setDebug
        23. xPDO.setLogLevel
        24. xPDO.setLogTarget
        25. xPDO.setOption
        26. xPDO.toJSON
      2. xPDOCacheManager
        1. xPDOCacheManager.copyFile
        2. xPDOCacheManager.copyTree
        3. xPDOCacheManager.delete
        4. xPDOCacheManager.deleteTree
        5. xPDOCacheManager.endsWith
        6. xPDOCacheManager.escapeSingleQuotes
        7. xPDOCacheManager.get
        8. xPDOCacheManager.getCachePath
        9. xPDOCacheManager.getCacheProvider
        10. xPDOCacheManager.matches
        11. xPDOCacheManager.replace
        12. xPDOCacheManager.set
        13. xPDOCacheManager.writeFile
        14. xPDOCacheManager.writeTree
      3. xPDOGenerator
        1. xPDOGenerator.parseSchema
        2. xPDOGenerator.writeSchema
      4. xPDOManager
        1. xPDOManager.createObjectContainer
        2. xPDOManager.createSourceContainer
        3. xPDOManager.removeObjectContainer
        4. xPDOManager.removeSourceContainer
      5. xPDOObject
        1. Configuration Accessors
          1. getOption
          2. setOption
        2. Field Accessors
          1. fromArray
          2. fromJSON
          3. get
          4. set
          5. toArray
          6. toJSON
        3. Metadata Accessors
          1. getFieldName
          2. getFKClass
          3. getFKDefinition
          4. getPK
          5. getPKType
          6. getSelectColumns
        4. Persistence Methods
          1. remove
          2. save
        5. Related Object Accessors
          1. addMany
          2. addOne
          3. getMany
          4. getOne
        6. State Accessors
          1. isDirty
          2. isLazy
          3. isNew
        7. Static Object Loaders
          1. _loadCollectionInstance
          2. _loadInstance
          3. _loadRows
          4. load
          5. loadCollection
          6. loadCollectionGraph
          7. Using Custom Loader Classes
        8. Validation
          1. addValidationRule
          2. getValidator
          3. isValidated
          4. removeValidationRules
          5. validate
      6. xPDOQuery
        1. xPDOQuery.andCondition
        2. xPDOQuery.groupby
        3. xPDOQuery.innerJoin
        4. xPDOQuery.leftJoin
        5. xPDOQuery.limit
        6. xPDOQuery.orCondition
        7. xPDOQuery.rightJoin
        8. xPDOQuery.select
        9. xPDOQuery.setClassAlias
        10. xPDOQuery.sortby
        11. xPDOQuery.where
      7. xPDORevisionControl
      8. xPDOTransport
        1. xPDOTransport.install
        2. xPDOTransport.pack
        3. xPDOTransport.uninstall
      9. xPDOValidator
        1. xPDOValidator.addMessage
        2. xPDOValidator.getMessages
        3. xPDOValidator.hasMessages
        4. xPDOValidator.validate



getCollectionGraph allows you to automatically load up related objects by specifying a JSON style hash to its second argument (in other words, it automatically joins a table on its related tables). It's possible to nest the JSON hash so you also retrieve the related objects of the related objects, for example:

$blogpost = $modx->getCollectionGraph('BlogPost', '{ "Comments":{} }', 34 );
foreach ( $blogpost->Comments as $c ) {
/* ...do something with each comment... */
// OR, joining on related objects of related objects
$TFR = $modx->getCollectionGraph('TrackingformsResources', '{ "Resources":{ "MassUnit":{}, "VolumeUnit":{} } }', 123 );

xPDO translates this all into the necessary database query that joins on the appropriate tables (e.g. perhaps named trackingforms_resources, resources, and units) – like most of the xPDO methods, you refer to a model's tables/classes via their aliases.

You can *NOT* set a limit on a getCollectionGraph when using an xPDOQuery object - you will be limiting the total number of rows fetched, which is not the same as the total number of top level (eg BlogPost) objects. You can get very unexpected results by doing so.

Sample Snippet

The below example that specifies several related objects. In the Zip XML schema, there would be some sort of aggregate relationship defined for "TZ", "ST", and "CT".

$out = false;
$xpdo->setPackage('sw_zipCode', MODX_BASE_PATH.'wsw/model/', 'sw_');
$collection= $xpdo->getCollectionGraph('Zip', '{"TZ":{},"ST":{},"CT":{}}', $lookupZip);
foreach ($collection as $obj)
    if (is_object($obj))
        $out = $obj->toArray();                      // 'Zip'
        $out[timezone] = $obj->TZ->get('tzname');
        $out[state] = $obj->ST->get('statename');
        $out[county] = $obj->CT->get('countyname');        
return $out;
Note that when you are using $xpdo->newQuery() to filter the results and have multiple field names which are the same, for example an "id" field, in one or more of the different classes you join, xPDO will fail to return any result. Simply prefix your fieldname with the classname in that case, for example myClassName.id

Snippet Call



    [id] => 32117
    [city] => Daytona Beach
    [areacode] => 386
    [lat] => 29.233
    [lon] => -81.0479
    [sw_county_id] => 1800
    [sw_states_id] => 15
    [sw_timezones_id] => 4
    [timezone] => Eastern
    [state] => Florida
    [county] => Volusia

Equivalent MySQL

$xpdo->setPackage('sw_zipCode', MODX_BASE_PATH.'wsw/model/', 'sw_');
$collection= $xpdo->getCollectionGraph('Zip', '{"TZ":{},"ST":{},"CT":{}}', $lookupZip);


FROM `sw_zips` AS Z
LEFT JOIN `sw_county` AS CT ON CT.`id` = Z.`sw_county_id`
LEFT JOIN `sw_states` AS ST ON ST.`id` = Z.`sw_states_id`
LEFT JOIN `sw_timezones` AS TZ ON TZ.`id` = Z.`sw_timezones_id`
WHERE Z.`id` = 32117

The Schema

The following schema is greatly simplified for readability and this example:

class= had to be renamed to klass= to be presented in this document system.

Though a functional schema, this is not a final schema by any stretch of the imagination.  In fact this is during the second stage of normalization, and is here for example purposes only.

<model package="sw_zipCode" baseClass="xPDOObject" platform="mysql" defaultEngine="MyISAM">
<object klass="class="City" table="city" extends="xPDOSimpleObject">
<field key="cityname" dbtype="varchar" precision="50" phptype="string" null="false"/>
<object klass="Cityzip" table="cityzip" extends="xPDOSimpleObject">
<field key="city" dbtype="int" precision="10" phptype="integer" null="false"/>
<field key="zip" dbtype="int" precision="5" phptype="integer" null="false"/>
<object klass="County" table="county" extends="xPDOSimpleObject">
<field key="countyname" dbtype="varchar" precision="35" phptype="string" null="true" index="index"/>
<object klass="States" table="states" extends="xPDOSimpleObject">
<field key="statename" dbtype="varchar" precision="40" phptype="string" null="false" index="index"/>
<field key="abbrv" dbtype="char" precision="2" phptype="string" null="false"/>
<object klass="Timezones" table="timezones" extends="xPDOSimpleObject">
<field key="tzname" dbtype="varchar" precision="20" phptype="string" null="true" index="index"/>
<object klass="Zip" table="zips" extends="xPDOSimpleObject">
<field key="city" dbtype="varchar" precision="50" phptype="string" null="true"/>
<field key="areacode" dbtype="int" precision="3" phptype="integer" null="true"/>
<field key="lat" dbtype="float" phptype="float" null="true"/>
<field key="lon" dbtype="float" phptype="float" null="true"/>
<field key="sw_county_id" dbtype="int" precision="4" phptype="integer" null="false" index="pk"/>
<field key="sw_states_id" dbtype="int" precision="2" phptype="integer" null="false" index="pk"/>
<field key="sw_timezones_id" dbtype="int" precision="2" phptype="integer" null="false" index="pk"/>
<aggregate alias="TZ" klass="Timezones" local="tz_id" foreign="id" cardinality="one" owner="foreign" />    
<aggregate alias="ST" klass="County" local="sw_county_id" foreign="id" cardinality="one" owner="foreign" />
<aggregate alias="CT" klass="States" local="sw_states_id" foreign="id" cardinality="one" owner="foreign" />

Another Example

Another relation example that is common is joining MODX pages with their Template Variable values. Sometimes this does not work as expected since values are stored differently than you might expect. But here's a walk-through.

$pages = $modx->getCollectionGraph('modResource', '{"TemplateVarResources":{}}', array('parent'=>12));

foreach ($pages as $p) {
        foreach ($p->TemplateVarResources as $tv) {
                // Do stuff here with the TV
                $tv_array = $tv->toArray();


  1. Obtain a connection via the xPDO Constructor including  Hydrating Fields
  2. Viewing the package name in the schema we set (or apply) the package to our connection, taking note of the prefix our tables are using in the database
  3.  Using 'Zip' as our "view" we look at the relationships directly defined in the Zip object, in our schema, and access those via the aliases given there

Additional Notes:

Everything is about the schema definition. A poorly thought out and developed schema may very well lead to many hours of frustration.

If you are having trouble with xPDO, you have two main avenues of troubleshooting:

  1. First and foremost -- the schema is not correct.  Re thinking it from the bottom relations up, and through each relationship may help us "see" where we may be missing it.
  2. Not understanding what we are seeing is another huge issue.
    1. Understand the point of your schema.  If your schema will eventually instantiate an object representing a single entity (such as a user) your base relationships should be ($this->user) 1:  1 or many on the other side.
    2. A relationship tied to a many-to-many relation (as in the relations between users and groups) will probably need a for each loop to filter through the sub relation.
    3. Aggregate relations should typically be singular.  Removing them does nothing to the related data
    4. Composite relations should typically be plural.  Removing them also removes each of the related child relations.
    5. Don't be afraid to use regular language in your schema. Instead of Cityzip, in the schema above, Cityhaszips might be a bit clearer in thinking through your schema
    6. Don't use the same class name in multiple places in the schema.  Not only will it bring  confusion while coding, I suspect it also confuses xPDO.  If for no other reason -- its just bad form.
    7. xPDO is fast, very fast.  If your queries are taking to long, go back to the schema and follow the indexes.
    8. xPDO likes primary keys, so build your relations around primary keys when ever possible -- if not always.
    9. In case you missed it '{"TZ":{},"ST":{},"CT":{}}' is JSON formatted.

Suggest an edit to this page on GitHub (Requires GitHub account. Opens a new window/tab) or become an editor of the MODX Documentation.