Entity withAliases
In 2.0, when you fetched an entity (or a collection of them), you could eagerly fetch specific relationships using the
with()
finder method. However, this could cause some problems. It could lead to code duplication, where you were naming the same joins in numerous places. Further, it was hard for add-ons to extend. We attempted to workaround this in several entity-specific finders by providing methods specifically for listing eager joins (like in
Finder\Thread::forFullView()
), but this only covered certain cases.
Enter the
withAliases
concept. Let's look at an example for threads:
Code:
$structure->withAliases = [
'full' => [
'User',
function()
{
$userId = \XF::visitor()->user_id;
if ($userId)
{
return [
'Read|' . $userId,
'UserPosts|' . $userId,
'Watch|' . $userId,
'Bookmarks|' . $userId
];
}
return null;
}
],
'fullForum' => [
'full',
function()
{
$with = ['Forum', 'Forum.Node'];
$userId = \XF::visitor()->user_id;
if ($userId)
{
$with[] = 'Forum.Read|' . $userId;
$with[] = 'Forum.Watch|' . $userId;
}
return $with;
}
]
];
Here we've defined two aliases, "full" and "fullForum". We'll look at a different approach to this in a moment.
When fetching threads, these can be accessed anywhere we expose the "with" concept to. For example,
$threadFinder->with('full')
or
$em->find('XF:Thread', 123, 'full')
. Internally, these will be expanded to include any named relationships within them. If we detect a closure, then that code will be run and the closure can return the names of any relationships that should be included.
If we look at the "fullForum" definition, we can see the first line is "full", which refers to the other
withAlias
we specified.
We can also refer to aliases on other entities. For example, if a "full" alias existed on the User relation, we could refer to
User.full
to automatically include all of the relationships that are defined in that case.
But let's look at another approach we could take instead of defining two different aliases, using the "with params" concept. XenForo uses the convention of using a vertical pipe to separate the name of a relationship to some sort of parameter to pass into it. That can be seen here with how we refer to fetching the read marking state for a particular user. With aliases support a similar idea, with the params passed into the closure.
Using this concept, we could define a single alias:
Code:
$structure->withAliases = [
'full' => [
'User',
function()
{
$userId = \XF::visitor()->user_id;
if ($userId)
{
return [
'Read|' . $userId, 'UserPosts|' . $userId,
'Watch|' . $userId,
'Bookmarks|' . $userId
];
}
return null;
},
function($withParams)
{
if (!empty($withParams['forum']))
{
$with = ['Forum', 'Forum.Node'];
$userId = \XF::visitor()->user_id;
if ($userId)
{
$with[] = 'Forum.Read|' . $userId;
$with[] = 'Forum.Watch|' . $userId;
}
return $with;
}
}
]
];
Make note of the last closure, which takes the
$withParams
argument. This will be populated based on our use of the pipe. For example, if we call
$threadFinder->with('full|forum')
, the "forum" key of the
$withParams
will be set to true. If we just call "full" without the pipe, then no parameters will be passed through.
Multiple parameters can be passed through using syntax like
alias|param1+param2
, which will set "param1" and "param2" to true. (The closures will also receive the finder and the raw string after the pipe, to allow more specific uses.)
While this may not sound like a particularly significant feature, it should allow add-ons to more consistently include their joins when needed and it has helped pave the way for a feature we'll be discussing soon.