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; }
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)); }
/** * 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()); }