public function testBelongsTo()
{
$rawly = Bug::findFirstById(1);
$rawly->robot;
$eagerly = Loader::fromModel(Bug::findFirstById(1), 'Robot');
$this->assertTrue(property_exists($eagerly, 'robot'));
$this->assertInstanceOf('Phalcon\\Test\\Mvc\\Model\\EagerLoading\\Stubs\\Robot', $eagerly->robot);
$this->assertEquals($rawly->robot->readAttribute('id'), $eagerly->robot->readAttribute('id'));
// Reverse
$rawly = Robot::findFirstById(2);
$rawly->bugs = $this->resultSetToEagerLoadingEquivalent($rawly->bugs);
$eagerly = Loader::fromModel(Robot::findFirstById(2), 'Bugs');
$this->assertTrue(property_exists($eagerly, 'bugs'));
$this->assertContainsOnlyInstancesOf('Phalcon\\Test\\Mvc\\Model\\EagerLoading\\Stubs\\Bug', $eagerly->bugs);
$getIds = function ($obj) {
return $obj->readAttribute('id');
};
$this->assertEquals(array_map($getIds, $rawly->bugs), array_map($getIds, $eagerly->bugs));
$this->assertEmpty(Loader::fromModel(Robot::findFirstById(1), 'Bugs')->bugs);
// Test from multiple
$rawly = $this->resultSetToEagerLoadingEquivalent(Bug::find(['limit' => 10]));
foreach ($rawly as $bug) {
$bug->robot;
}
$eagerly = Loader::fromResultset(Bug::find(array('limit' => 10)), 'Robot');
$this->assertTrue(is_array($eagerly));
$this->assertTrue(array_reduce($eagerly, function ($res, $bug) {
return $res && property_exists($bug, 'robot');
}, true));
$getIds = function ($obj) {
return property_exists($obj, 'robot') && isset($obj->robot) ? $obj->robot->readAttribute('id') : null;
};
$this->assertEquals(array_map($getIds, $rawly), array_map($getIds, $eagerly));
}
/**
* Executes each db query needed
*
* Note: The {$alias} property is set two times because Phalcon Model ignores
* empty arrays when overloading property set.
*
* Also {@see https://github.com/stibiumz/phalcon.eager-loading/issues/1}
*
* @return $this
*/
public function load()
{
$relation = $this->relation;
$alias = $relation->getOptions();
$alias = strtolower($alias['alias']);
$relField = $relation->getFields();
$relReferencedModel = $relation->getReferencedModel();
$relReferencedField = $relation->getReferencedFields();
$relIrModel = $relation->getIntermediateModel();
$relIrField = $relation->getIntermediateFields();
$relIrReferencedField = $relation->getIntermediateReferencedFields();
// PHQL has problems with this slash
if ($relReferencedModel[0] === '\\') {
$relReferencedModel = ltrim($relReferencedModel, '\\');
}
$bindValues = array();
foreach ($this->parent->getSubject() as $record) {
$bindValues[$record->readAttribute($relField)] = true;
}
$bindValues = array_keys($bindValues);
$subjectSize = count($this->parent->getSubject());
$isManyToManyForMany = false;
$builder = new QueryBuilder();
$builder->from($relReferencedModel);
if ($isThrough = $relation->isThrough()) {
if ($subjectSize === 1) {
// The query is for a single model
$builder->innerJoin($relIrModel, sprintf('[%s].[%s] = [%s].[%s]', $relIrModel, $relIrReferencedField, $relReferencedModel, $relReferencedField))->inWhere("[{$relIrModel}].[{$relIrField}]", $bindValues);
} else {
// The query is for many models, so it's needed to execute an
// extra query
$isManyToManyForMany = true;
$relIrValues = new QueryBuilder();
$relIrValues = $relIrValues->from($relIrModel)->inWhere("[{$relIrModel}].[{$relIrField}]", $bindValues)->getQuery()->execute()->setHydrateMode(Resultset::HYDRATE_ARRAYS);
$bindValues = $modelReferencedModelValues = array();
foreach ($relIrValues as $row) {
$bindValues[$row[$relIrReferencedField]] = true;
$modelReferencedModelValues[$row[$relIrField]][$row[$relIrReferencedField]] = true;
}
unset($relIrValues, $row);
$builder->inWhere("[{$relReferencedField}]", array_keys($bindValues));
}
} else {
$builder->inWhere("[{$relReferencedField}]", $bindValues);
}
if ($this->constraints) {
call_user_func($this->constraints, $builder);
}
$records = array();
if ($isManyToManyForMany) {
foreach ($builder->getQuery()->execute() as $record) {
$records[$record->readAttribute($relReferencedField)] = $record;
}
foreach ($this->parent->getSubject() as $record) {
$referencedFieldValue = $record->readAttribute($relField);
if (isset($modelReferencedModelValues[$referencedFieldValue])) {
$referencedModels = array();
foreach ($modelReferencedModelValues[$referencedFieldValue] as $idx => $_) {
$referencedModels[] = $records[$idx];
}
$record->{$alias} = $referencedModels;
if (static::$isPhalcon2) {
$record->{$alias} = null;
$record->{$alias} = $referencedModels;
}
} else {
$record->{$alias} = null;
$record->{$alias} = array();
}
}
$records = array_values($records);
} else {
// We expect a single object or a set of it
$isSingle = !$isThrough && ($relation->getType() === Relation::HAS_ONE || $relation->getType() === Relation::BELONGS_TO);
if ($subjectSize === 1) {
// Keep all records in memory
foreach ($builder->getQuery()->execute() as $record) {
$records[] = $record;
}
$record = $this->parent->getSubject();
$record = $record[0];
if ($isSingle) {
$record->{$alias} = empty($records) ? null : $records[0];
} else {
if (empty($records)) {
$record->{$alias} = null;
$record->{$alias} = array();
} else {
$record->{$alias} = $records;
if (static::$isPhalcon2) {
$record->{$alias} = null;
$record->{$alias} = $records;
}
}
}
} else {
$indexedRecords = array();
// Keep all records in memory
foreach ($builder->getQuery()->execute() as $record) {
$records[] = $record;
if ($isSingle) {
$indexedRecords[$record->readAttribute($relReferencedField)] = $record;
} else {
$indexedRecords[$record->readAttribute($relReferencedField)][] = $record;
}
}
foreach ($this->parent->getSubject() as $record) {
$referencedFieldValue = $record->readAttribute($relField);
if (isset($indexedRecords[$referencedFieldValue])) {
$record->{$alias} = $indexedRecords[$referencedFieldValue];
if (static::$isPhalcon2 && is_array($indexedRecords[$referencedFieldValue])) {
$record->{$alias} = null;
$record->{$alias} = $indexedRecords[$referencedFieldValue];
}
} else {
$record->{$alias} = null;
if (!$isSingle) {
$record->{$alias} = array();
}
}
}
}
}
$this->subject = $records;
return $this;
}
/**
* At original repo
*/
public function testIssue4()
{
// Has many -> Belongs to
// Should be the same for Has many -> Has one
$loader = new Loader(Robot::findFirstById(1), 'Bugs.Robot');
$this->assertEquals($loader->execute()->get()->bugs, array());
}
public function testHasManyToMany()
{
$rawly = Robot::findFirstById(1);
$rawly->parts;
$eagerly = Loader::fromModel(Robot::findFirstById(1), 'Parts');
$this->assertTrue(property_exists($eagerly, 'parts'));
$this->assertTrue(is_array($eagerly->parts));
$this->assertSame(count($eagerly->parts), $rawly->parts->count());
$getIds = function ($arr) {
$ret = array();
foreach ($arr as $r) {
if (is_object($r)) {
$ret[] = $r->readAttribute('id');
}
}
return $ret;
};
$this->assertEquals($getIds($this->resultSetToEagerLoadingEquivalent($rawly->parts)), $getIds($eagerly->parts));
}