/**
* Return entities from an SQL query generated by elgg_get_entities.
*
* @access private
*
* @param string $sql
* @param ElggBatch $batch
* @return ElggEntity[]
* @throws LogicException
*/
public function fetchFromSql($sql, \ElggBatch $batch = null)
{
$plugin_subtype = $this->subtype_table->getId('object', 'plugin');
// Keys are types, values are columns that, if present, suggest that the secondary
// table is already JOINed. Note it's OK if guess incorrectly because entity load()
// will fetch any missing attributes.
$types_to_optimize = array('object' => 'title', 'user' => 'password_hash', 'group' => 'name', 'site' => 'url');
$rows = $this->db->getData($sql);
// guids to look up in each type
$lookup_types = array();
// maps GUIDs to the $rows key
$guid_to_key = array();
if (isset($rows[0]->type, $rows[0]->subtype) && $rows[0]->type === 'object' && $rows[0]->subtype == $plugin_subtype) {
// Likely the entire resultset is plugins, which have already been optimized
// to JOIN the secondary table. In this case we allow retrieving from cache,
// but abandon the extra queries.
$types_to_optimize = array();
}
// First pass: use cache where possible, gather GUIDs that we're optimizing
foreach ($rows as $i => $row) {
if (empty($row->guid) || empty($row->type)) {
throw new LogicException('Entity row missing guid or type');
}
// We try ephemeral cache because it's blazingly fast and we ideally want to access
// the same PHP instance. We don't try memcache because it isn't worth the overhead.
$entity = $this->entity_cache->get($row->guid);
if ($entity) {
// from static var, must be refreshed in case row has extra columns
$entity->refresh($row);
$rows[$i] = $entity;
continue;
}
if (isset($types_to_optimize[$row->type])) {
// check if row already looks JOINed.
if (isset($row->{$types_to_optimize[$row->type]})) {
// Row probably already contains JOINed secondary table. Don't make another query just
// to pull data that's already there
continue;
}
$lookup_types[$row->type][] = $row->guid;
$guid_to_key[$row->guid] = $i;
}
}
// Do secondary queries and merge rows
if ($lookup_types) {
foreach ($lookup_types as $type => $guids) {
$set = "(" . implode(',', $guids) . ")";
$sql = "SELECT * FROM {$this->db->prefix}{$type}s_entity WHERE guid IN {$set}";
$secondary_rows = $this->db->getData($sql);
if ($secondary_rows) {
foreach ($secondary_rows as $secondary_row) {
$key = $guid_to_key[$secondary_row->guid];
// cast to arrays to merge then cast back
$rows[$key] = (object) array_merge((array) $rows[$key], (array) $secondary_row);
}
}
}
}
// Second pass to finish conversion
foreach ($rows as $i => $row) {
if ($row instanceof ElggEntity) {
continue;
} else {
try {
$rows[$i] = $this->rowToElggStar($row);
} catch (IncompleteEntityException $e) {
// don't let incomplete entities throw fatal errors
unset($rows[$i]);
// report incompletes to the batch process that spawned this query
if ($batch) {
$batch->reportIncompleteEntity($row);
}
}
}
}
return $rows;
}