Retrieving Objects
Last updated Mar 29th, 2021 | Page history | Improve this page | Report an issue
Support the team building MODX with a monthly donation.
The budget raised through OpenCollective is transparent, including payouts, and any contributor can apply to be paid for their work on MODX.
Backers
Budget
$301 per month—let's make that $500!
Learn moreTo get Objects in xPDO, there are a variety of methods. The two basic themes we'll concern ourselves with here is in the differences between an Object, a Collection, and an Iterator.
An Object is a single xPDOObject
, nothing more, nothing less. It is retrieved via xPDO::getObject()
. Think of it like one row in a database table.
A Collection is an array of xPDOObjects
. They are retrieved via xPDO::getCollection
. Think of it as a list of rows in a table.
An Iterator is a special kind of Collection which is accessed sequentially so that all of the rows and objects representing them are not loaded into memory all at once.
Important: do not pass untrusted input into the criteria parameter of the
xPDO::getObject()
or related methods. Always sanitize the value, cast the value to integer for a primary key (getObject
/getObjectGraph
), or provide the criteria in array form if it comes from any sort of user input to prevent potential SQL injections or returning data not supposed to be accessible. xPDO attempts to identify and prevent SQL injections, however as it also supports providing raw SQL criteria in several cases, not sanitizing the criteria may make your code vulnerable.Similarly do not pass untrusted input to the
sortby()
method on anxPDOQuery
object. The direction only acceptsASC
orDESC
, however the column accepts any valid SQL clause which may include things you don't want. Checking a value against acceptable column names is the most secure option.
xPDO::getObject¶
getObject
takes 3 arguments: $className
, $criteria
, and $cacheFlag
. The first argument is the class name you'd like to retrieve; the second is the criteria by which you'd like to search for it; and the final argument is the caching option for the object.
If an integer value is provided for the $cacheFlag
argument, then it specifies the time to live in the object cache; if cacheFlag === false, caching is ignored for the object and if cacheFlag === true, the object will live in the cache indefinitely.
Back to $criteria
. This can be one of 4 things:
- A primary key value
- An array of field(s)
- Raw SQL representing a valid condition
- An xPDOCriteria object or derivative.
We'll get back to the fourth option later. First, an example of the first option:
$box23 = $xpdo->getObject('Box', 23);
When using this approach, always sanitize your input, for example by casting a user-provided value to an integer first.
If an object doesn't exist, it will return 'null'.
You can specify multiple filter criteria inside your 2nd argument:
// GroupUser is a table with 2 PKs - 'user' and 'group'
$gu = $xpdo->getObject('GroupUser', [
'user' => 12,
'group' => 4,
]);
Or, let's say we wanted to grab the first Box object we find with a width of 150:
$bigbox = $xpdo->getObject('Box', ['width' => 150]);
It's also possible to provide a raw SQL condition to getObject as a string:
$bigbox = $xpdo->getObject('Box', '`width` = 150');
Never use this approach with untrusted user input. Use of the array syntax is very strongly recommended to make use of prepared statements and be protected against SQL injection or accessing information the user is not supposed to.
Good to know: If your criteria matches multiple objects, getObject will only return the first one. Which one is first depends on the database and its natural sorting.
We'll discuss the fourth option, xPDOCriteria
, later in the xPDOQuery
and xPDOCriteria
sections.
xPDO::getCollection¶
xPDO::getCollection
takes the same three arguments as getObject
; except the $criteria
field must be either an array or xPDOCriteria
object.
Let's say we wanted to grab all the Box objects with width of 14:
// assume we have 3 boxes
$boxes = $xpdo->getCollection('Box', [
'width' => 14,
]);
foreach ($boxes as $box) {
echo $box->get('color')."\n";
}
// If our 3 boxes had a color of 'red','blue' and 'yellow', this would print:
// red
// blue
// yellow
xPDO::getIterator¶
The xPDO::getIterator
method is identical to xPDO::getCollection
except that you can only access one xPDOObject
from the Collection of rows at one time. If you need access to all of the objects/rows at once, use getCollection
. Otherwise, it is more efficient in terms of memory usage, to use getIterator to loop through a Collection of `xPDOObjects.
The code for iterating over the Box objects with width of 14 is almost identical to that of getCollection
:
// assume we have 3 boxes, with colors 'red','blue' and 'yellow'
$boxes = $xpdo->getIterator('Box', [
'width' => 14,
]);
foreach ($boxes as $box) {
echo $box->get('color')."\n";
}
// this would print:
// red
// blue
// yellow
Note that the index for each object when iterated over is not the primary key, unlike the array index when using getCollection.
xPDO::newQuery¶
One of the most powerful parts of xPDO is its ability to create complex queries in a simple fashion using the xPDOQuery wrapper. This class lets you build SQL queries using OOP methods that extend the xPDOCriteria
class – you can pass its instance right into getObject or getCollection calls. The newQuery function creates an xPDOQuery object, and takes 3 parameters:
-
$class
- The class name to create the query for. -
$criteria
- This is optional; but you can specify criteria here. -
$cacheFlag
- Similar to getObject's cacheFlag, you can specify the caching behavior for this query.
First, let's just show how you might use newQuery to define the criteria we used before: width = 14. We'll just add a sorting option to sort the results.
$c = $xpdo->newQuery('Box');
$c->where(['width' => 14]);
$c->sortby('name', 'ASC');
$boxes = $xpdo->getCollection('Box',$c);
Important: do not allow untrusted user input directly into the
$c->sortby()
method. The order only acceptsASC
orDESC
, but the column to sort by may be any valid SQL clause which can lead to unexpected results.
Once you have your result, you can iterate over the array (see above). You can see the similarity between the defining a query object and passing a simple array to getObject or getCollection. So why use xPDOQuery? It's more flexible. Did you see how we could use it to specify the sorting order?
Next, let's use the query to join on a related table using xPDOQuery.innerJoin. Let's create an example query using xPDOQuery that will grab the first 5 Boxes with width of 5 and an owner of ID 2, sorted by their name. Our "Box" table has a many-to-many relationship with the "BoxOwner" table.
$c = $xpdo->newQuery('Box');
$c->innerJoin('BoxOwner','Owner'); // arguments are className, alias
$c->where([
'width' => 5,
'Owner.user' => 2,
]);
$c->sortby('name','ASC');
$c->limit(5);
$boxes = $xpdo->getCollection('Box',$c);
We can join on 3rd table ("Owner") by using another call to xPDOQuery.innerJoin. Let's also grab the 2nd 5 Boxes by specifying an offset – it's a 2nd argument to the limit() function:
$c = $xpdo->newQuery('Box');
$c->innerJoin('BoxOwner','Owner'); // arguments are: className, alias
$c->innerJoin('User','User','Owner.user = User.id');
// note the 3rd argument that defines the relationship in the innerJoin
$c->where([
'Box.width' => 5,
'User.user' => 2,
]);
$c->sortby('Box.name','ASC');
$c->limit(5, 5); // limit, offset
$boxes = $xpdo->getCollection('Box',$c);
You can see that the sortby and where functions can take dot syntax on their parameters; they can prefix their columns with alias – sometimes they have to do this to prevent collisions!
More information on xPDOQuery can be found here.
The xml schema can be found in your MODX installation's core folder, here: model/schema/modx.mysql.schema.xml (helpful to get classNames, aliases, etc for your queries)
Debugging¶
As the ORM layer takes care of the queries and doesn't expose them to you normally, if things don't work as expected it may be useful to get access to what xPDO will actually ask of the database. The following code can be used to show the SQL for an xPDOQuery:
$c = $xpdo->newQuery('Box');
// ... add some more criteria...
$c->prepare();
print $c->toSQL();
Graphs¶
A "graph" extends the idea of an object (or objects in collections). Instead of a simple object, a graph includes references to related objects. Graphs are a useful alternative to JOINs.
xPDO::getObjectGraph¶
This is the same as getCollectionGraph, but it returns a single object. See getCollectionGraph below for info.
xPDO::getCollectionGraph¶
$collection= $xpdo->getCollectionGraph('Zip', '{"TZ":{},"ST":{},"CT":{}}');
if ($collection) {
foreach ($collection as $obj) {
$out = $obj->toArray();
$out['timezone'] = $obj->TZ->get('tzname');
$out['state'] = $obj->ST->get('statename');
$out['county'] = $obj->CT->get('countyname');
print_r($out);
}
}
Aliases in JSON: Remember that the JSON hash passed to
getObjectGraph
orgetCollectionGraph
needs to use aliases, not class names.
You have direct access to all of the fields (table rows) in the Collection Graph comprised in these four tables. The alias is used to create the graph. In this example, the 'Zip' table is the primary table, so we look at that table and we define relationships from the perspective of that primary table.
As with getObject and getCollection, we can supply a $criteria
object to getCollectionGraph. Let's add some criteria to our getCollectionGraph()
query. In this example, we can search for zipcodes in California (CA)
$criteria = $modx->newQuery('Zip');
$criteria->where(['ST.statename' => 'CA']);
$collection = $xpdo->getCollectionGraph('Zip', '{"TZ":{},"ST":{},"CT":{}}', $criteria);
if ($collection) {
foreach ($collection as $obj) {
$out = $obj->toArray();
$out['timezone'] = $obj->TZ->get('tzname');
$out['state'] = $obj->ST->get('statename');
$out['county'] = $obj->CT->get('countyname');
print_r($out);
}
}
Aliases in Criteria: The table names you specify in your criteria must use the aliases, not the class names (just like the JSON hashes).
Let's show one more example, this time using MODX tables. This is only an example: filtering on Template Variables is a bit dangerous because the values stored in the database are not always the verbatim values you experience in the manager or in your templates. But this example should help demonstrate the usage of aliases and that you must be aware of the relationships between the objects (some related objects are singular, some are arrays).
$criteria = [];
$criteria['modResource.id:IN'] = [1,2,3];
$criteria['TemplateVarResources.tmplvarid'] = 5;
$criteria = $modx->newQuery('modResource', $criteria);
$pages = $modx->getCollectionGraph('modResource', '{"TemplateVarResources":{"TemplateVar":{}}}', $criteria);
if ($pages) {
foreach ($pages as $p) {
print $p->get('pagetitle');
foreach ($p->TemplateVarResources as $tvr) {
$name = $tvr->TemplateVar->get('name');
print $name . ' is '. $tvr->get('value');
}
}
}
Please view the dedicated page: getCollectionGraph