/** * Constructor * * @param EntityCache $entity_cache Entity cache * @param EntityTable $entity_table Entity service */ public function __construct(EntityCache $entity_cache, EntityTable $entity_table) { $this->_callable_cache_checker = function ($guid) use($entity_cache) { return $entity_cache->get($guid); }; $this->_callable_entity_loader = function ($options) use($entity_table) { return $entity_table->getEntities($options); }; }
/** * 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; }