diff --git a/src/ORM/DataList.php b/src/ORM/DataList.php index 3c6a973e3..f33685dbd 100644 --- a/src/ORM/DataList.php +++ b/src/ORM/DataList.php @@ -11,8 +11,10 @@ use Exception; use InvalidArgumentException; use LogicException; use BadMethodCallException; +use SilverStripe\ORM\Connect\Query; use Traversable; use SilverStripe\ORM\DataQuery; +use SilverStripe\ORM\ArrayList; /** * Implements a "lazy loading" DataObjectSet. @@ -58,6 +60,11 @@ class DataList extends ViewableData implements SS_List, Filterable, Sortable, Li */ protected $finalisedQuery; + + private array $eagerLoadRelations = []; + + private array $eagerLoadedData = []; + /** * Create a new DataList. * No querying is done on construction, but the initial query schema is set up. @@ -824,8 +831,7 @@ class DataList extends ViewableData implements SS_List, Filterable, Sortable, Li */ public function toArray() { - $query = $this->dataQuery->query(); - $rows = $query->execute(); + $rows = $this->executeQuery(); $results = []; foreach ($rows as $row) { @@ -916,10 +922,60 @@ class DataList extends ViewableData implements SS_List, Filterable, Sortable, Li $creationType = empty($row['ID']) ? DataObject::CREATE_OBJECT : DataObject::CREATE_HYDRATED; $item = Injector::inst()->create($class, $row, $creationType, $this->getQueryParams()); - + $this->setDataObjectEagerLoadedData($item); return $item; } + private function setDataObjectEagerLoadedData(DataObject $item): void + { + foreach (array_keys($this->eagerLoadedData) as $eagerLoadRelation) { + list($dataClasses, $relations) = $this->getEagerLoadVariables($eagerLoadRelation); + $dataClass = $dataClasses[count($dataClasses) - 2]; + $relation = $relations[count($relations) - 1]; + foreach (array_keys($this->eagerLoadedData[$eagerLoadRelation]) as $eagerLoadID) { + $eagerLoadedData = $this->eagerLoadedData[$eagerLoadRelation][$eagerLoadID][$relation]; + if ($dataClass === $dataClasses[0]) { + if ($eagerLoadID === $item->ID) { + $item->setEagerLoadedData($relation, $eagerLoadedData); + } + } elseif ($dataClass === $dataClasses[1]) { + $relationData = $item->{$relations[1]}(); + if ($relationData instanceof DataObject) { + if ($relationData->ID === $eagerLoadID) { + $subItem = $relationData; + } else { + $subItem = null; + } + } else { + $subItem = $item->{$relations[1]}()->find('ID', $eagerLoadID); + } + if ($subItem) { + $subItem->setEagerLoadedData($relations[2], $eagerLoadedData); + } + } elseif ($dataClass === $dataClasses[2]) { + $relationData = $item->{$relations[1]}(); + if ($relationData instanceof DataObject) { + $list = new ArrayList([$relationData]); + } else { + $list = $relationData; + } + foreach ($list as $subItem) { + $subRelationData = $subItem->{$relations[2]}(); + if ($relationData instanceof DataObject) { + $subList = new ArrayList([$subRelationData]); + } else { + $subList = $subRelationData; + } + $subSubItem = $subList->find('ID', $eagerLoadID); + if ($subSubItem) { + $subSubItem->setEagerLoadedData($relations[3], $eagerLoadedData); + } + } + } + } + } + } + /** * Get query parameters for this list. * These values will be assigned as query parameters to newly created objects from this list. @@ -956,12 +1012,227 @@ class DataList extends ViewableData implements SS_List, Filterable, Sortable, Li protected function getFinalisedQuery() { if (!$this->finalisedQuery) { - $this->finalisedQuery = $this->dataQuery->query()->execute(); + $this->finalisedQuery = $this->executeQuery(); } return $this->finalisedQuery; } + private function getEagerLoadVariables(string $eagerLoadRelation): array + { + $schema = DataObject::getSchema(); + $relations = array_merge(['root'], explode('.', $eagerLoadRelation)); + $dataClasses = [$this->dataClass]; + $hasOneIDField = null; + $belongsToIDField = null; + $hasManyIDField = null; + $manyManyLastComponent = null; + for ($i = 0; $i < count($relations) - 1; $i++) { + $hasOneComponent = $schema->hasOneComponent($dataClasses[$i], $relations[$i + 1]); + if ($hasOneComponent) { + $dataClasses[] = $hasOneComponent; + $hasOneIDField = $relations[$i + 1] . 'ID'; + continue; + } + $belongsToComponent = $schema->belongsToComponent($dataClasses[$i], $relations[$i + 1]); + if ($belongsToComponent) { + $dataClasses[] = $belongsToComponent; + $belongsToIDField = $schema->getRemoteJoinField($dataClasses[$i], $relations[$i + 1], 'belongs_to'); + continue; + } + $hasManyComponent = $schema->hasManyComponent($dataClasses[$i], $relations[$i + 1]); + if ($hasManyComponent) { + $dataClasses[] = $hasManyComponent; + $hasManyIDField = $schema->getRemoteJoinField($dataClasses[$i], $relations[$i + 1], 'has_many'); + continue; + } + // this works for both many_many and belongs_many_many + $manyManyComponent = $schema->manyManyComponent($dataClasses[$i], $relations[$i + 1]); + if ($manyManyComponent) { + $dataClasses[] = $manyManyComponent['childClass']; + $manyManyLastComponent = $manyManyComponent; + continue; + } + throw new InvalidArgumentException("Invalid relation passed to eagerLoad() - $eagerLoadRelation"); + } + return [$dataClasses, $relations, $hasOneIDField, $belongsToIDField, $hasManyIDField, $manyManyLastComponent]; + } + + private function executeQuery(): Query + { + $query = $this->dataQuery->query()->execute(); + $this->fetchEagerLoadRelations($query); + return $query; + } + + private function fetchEagerLoadRelations(Query $query): void + { + if (empty($this->eagerLoadRelations)) { + return; + } + $ids = $query->column('ID'); + if (empty($ids)) { + return; + } + $topLevelIDs = $ids; + // Using ->toArray() and then iterating instead of just iterating DataList because + // in some instances it prevents some extra SQL queries + $prevRelationArray = []; + foreach ($this->eagerLoadRelations as $eagerLoadRelation) { + list( + $dataClasses, + $relations, + $hasOneIDField, + $belongsToIDField, + $hasManyIDField, + $manyManyLastComponent + ) = $this->getEagerLoadVariables($eagerLoadRelation); + $dataClass = $dataClasses[count($dataClasses) - 2]; + $relation = $relations[count($relations) - 1]; + $relationDataClass = $dataClasses[count($dataClasses) - 1]; + if ($dataClass === $this->dataClass) { + // When we're at "the top of a tree of nested relationships", we can just use the IDs from the query + // This is important to do when handling multiple eager-loaded relatioship trees. + $ids = $topLevelIDs; + } + // has_one + if ($hasOneIDField) { + $itemArray = []; + $relationItemIDs = []; + if ($dataClass === $dataClasses[0]) { + while ($row = $query->record()) { + $itemArray[] = [ + 'ID' => $row['ID'], + $hasOneIDField => $row[$hasOneIDField] + ]; + $relationItemIDs[] = $row[$hasOneIDField]; + } + } else { + foreach ($prevRelationArray as $itemData) { + $itemArray[] = [ + 'ID' => $itemData->ID, + $hasOneIDField => $itemData->$hasOneIDField + ]; + $relationItemIDs[] = $itemData->$hasOneIDField; + } + } + $relationArray = DataObject::get($relationDataClass)->filter(['ID' => $relationItemIDs])->toArray(); + foreach ($itemArray as $itemData) { + foreach ($relationArray as $relationItem) { + $eagerLoadID = $itemData['ID']; + if ($relationItem->ID === $itemData[$hasOneIDField]) { + $this->eagerLoadedData[$eagerLoadRelation][$eagerLoadID][$relation] = $relationItem; + } + } + } + $prevRelationArray = $relationArray; + $ids = $relationItemIDs; + // belongs_to + } elseif ($belongsToIDField) { + $relationArray = DataObject::get($relationDataClass)->filter([$belongsToIDField => $ids])->toArray(); + $relationItemIDs = []; + foreach ($relationArray as $relationItem) { + $relationItemIDs[] = $relationItem->ID; + $eagerLoadID = $relationItem->$belongsToIDField; + $this->eagerLoadedData[$eagerLoadRelation][$eagerLoadID][$relation] = $relationItem; + } + $prevRelationArray = $relationArray; + $ids = $relationItemIDs; + // has_many + } elseif ($hasManyIDField) { + $relationArray = DataObject::get($relationDataClass)->filter([$hasManyIDField => $ids])->toArray(); + $relationItemIDs = []; + foreach ($relationArray as $relationItem) { + $relationItemIDs[] = $relationItem->ID; + $eagerLoadID = $relationItem->$hasManyIDField; + if (!isset($this->eagerLoadedData[$eagerLoadRelation][$eagerLoadID][$relation])) { + $arrayList = ArrayList::create(); + $arrayList->setDataClass($relationItem->dataClass); + $this->eagerLoadedData[$eagerLoadRelation][$eagerLoadID][$relation] = $arrayList; + } + $this->eagerLoadedData[$eagerLoadRelation][$eagerLoadID][$relation]->push($relationItem); + } + $prevRelationArray = $relationArray; + $ids = $relationItemIDs; + // many_many + belongs_many_many & many_many_through + } elseif ($manyManyLastComponent) { + $parentField = $manyManyLastComponent['parentField']; + $childField = $manyManyLastComponent['childField']; + // $join will either be: + // - the join table name for many-many + // - the join data class for many-many-through + $join = $manyManyLastComponent['join']; + // many_many_through + if (is_a($manyManyLastComponent['relationClass'], ManyManyThroughList::class, true)) { + $joinThroughObjs = $join::get()->filter([$parentField => $ids]); + $relationItemIDs = []; + $rows = []; + foreach ($joinThroughObjs as $joinThroughObj) { + $rows[] = [ + $parentField => $joinThroughObj->$parentField, + $childField => $joinThroughObj->$childField + ]; + $relationItemIDs[] = $joinThroughObj->$childField; + } + // many_many + belongs_many_many + } else { + $joinTableQuery = DB::query('SELECT * FROM "' . $join . '" WHERE "' . $parentField . '" IN (' . implode(',', $ids) . ')'); + $relationItemIDs = []; + $rows = []; + while ($row = $joinTableQuery->record()) { + $rows[] = [ + $parentField => $row[$parentField], + $childField => $row[$childField] + ]; + $relationItemIDs[] = $row[$childField]; + } + } + $relationArray = DataObject::get($relationDataClass)->filter(['ID' => $relationItemIDs])->toArray(); + foreach ($rows as $row) { + $eagerLoadID = $row[$parentField]; + if (!isset($this->eagerLoadedData[$eagerLoadRelation][$eagerLoadID][$relation])) { + $arrayList = ArrayList::create(); + $arrayList->setDataClass($manyManyLastComponent['childClass']); + $this->eagerLoadedData[$eagerLoadRelation][$eagerLoadID][$relation] = $arrayList; + } + $relationItem = array_values(array_filter($relationArray, function ($relationItem) use ($row, $childField) { + return $relationItem->ID === $row[$childField]; + }))[0]; + $this->eagerLoadedData[$eagerLoadRelation][$eagerLoadID][$relation]->push($relationItem); + } + $prevRelationArray = $relationArray; + $ids = $relationItemIDs; + } else { + throw new LogicException('Something went wrong with the eager loading'); + } + } + } + + /** + * $myDataList->eagerLoad('Some.Nested.Relation', 'Another.Relation') + */ + public function eagerLoad(...$relations): static + { + $arr = []; + foreach ($relations as $relation) { + $parts = explode('.', $relation); + $count = count($parts); + if ($count > 3) { + $message = "Eager loading only supports up to 3 levels of nesting, passed $count levels - $relation"; + throw new InvalidArgumentException($message); + } + for ($i = 0; $i < $count; $i++) { + if ($i === 0) { + $arr[] = $parts[$i]; + } else { + $arr[] = $arr[count($arr) - 1] . '.' . $parts[$i]; + } + } + } + $this->eagerLoadRelations = array_merge($this->eagerLoadRelations, $arr); + return $this; + } + /** * Return the number of items in this DataList */ diff --git a/src/ORM/DataObject.php b/src/ORM/DataObject.php index c49a06c3b..fae101fd3 100644 --- a/src/ORM/DataObject.php +++ b/src/ORM/DataObject.php @@ -304,6 +304,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity */ protected $unsavedRelations; + private array $eagerLoadedData = []; + /** * List of relations that should be cascade deleted, similar to `owns` * Note: This will trigger delete on many_many objects, not only the mapping table. @@ -1802,6 +1804,9 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity */ public function getComponent($componentName) { + if (isset($this->eagerLoadedData[$componentName])) { + return $this->eagerLoadedData[$componentName]; + } if (isset($this->components[$componentName])) { return $this->components[$componentName]; } @@ -1924,6 +1929,11 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity return $this; } + public function setEagerLoadedData(string $eagerLoadRelation, mixed $eagerLoadedData): void + { + $this->eagerLoadedData[$eagerLoadRelation] = $eagerLoadedData; + } + /** * Returns a one-to-many relation as a HasManyList * @@ -1933,6 +1943,9 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity */ public function getComponents($componentName, $id = null) { + if (isset($this->eagerLoadedData[$componentName])) { + return $this->eagerLoadedData[$componentName]; + } if (!isset($id)) { $id = $this->ID; } @@ -2150,6 +2163,9 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity */ public function getManyManyComponents($componentName, $id = null) { + if (isset($this->eagerLoadedData[$componentName])) { + return $this->eagerLoadedData[$componentName]; + } if (!isset($id)) { $id = $this->ID; } diff --git a/tests/php/ORM/DataListTest.php b/tests/php/ORM/DataListTest.php index 587380c0d..d979c3282 100755 --- a/tests/php/ORM/DataListTest.php +++ b/tests/php/ORM/DataListTest.php @@ -26,6 +26,31 @@ use SilverStripe\ORM\Tests\DataObjectTest\Team; use SilverStripe\ORM\Tests\DataObjectTest\TeamComment; use SilverStripe\ORM\Tests\DataObjectTest\ValidatedObject; use SilverStripe\ORM\Tests\ManyManyListTest\Category; +use SilverStripe\ORM\Tests\DataListTest\EagerLoadObject; +use SilverStripe\ORM\Tests\DataListTest\HasOneEagerLoadObject; +use SilverStripe\ORM\Tests\DataListTest\HasOneSubEagerLoadObject; +use SilverStripe\ORM\Tests\DataListTest\HasOneSubSubEagerLoadObject; +use SilverStripe\ORM\Tests\DataListTest\BelongsToEagerLoadObject; +use SilverStripe\ORM\Tests\DataListTest\BelongsToSubEagerLoadObject; +use SilverStripe\ORM\Tests\DataListTest\BelongsToSubSubEagerLoadObject; +use SilverStripe\ORM\Tests\DataListTest\HasManyEagerLoadObject; +use SilverStripe\ORM\Tests\DataListTest\HasManySubEagerLoadObject; +use SilverStripe\ORM\Tests\DataListTest\HasManySubSubEagerLoadObject; +use SilverStripe\ORM\Tests\DataListTest\ManyManyEagerLoadObject; +use SilverStripe\ORM\Tests\DataListTest\ManyManySubEagerLoadObject; +use SilverStripe\ORM\Tests\DataListTest\ManyManySubSubEagerLoadObject; +use SilverStripe\ORM\Tests\DataListTest\ManyManyThroughEagerLoadObject; +use SilverStripe\ORM\Tests\DataListTest\ManyManyThroughSubEagerLoadObject; +use SilverStripe\ORM\Tests\DataListTest\ManyManyThroughSubSubEagerLoadObject; +use SilverStripe\ORM\Tests\DataListTest\EagerLoadObjectManyManyThroughEagerLoadObject; +use SilverStripe\ORM\Tests\DataListTest\ManyManyThroughEagerLoadObjectManyManyThroughSubEagerLoadObject; +use SilverStripe\ORM\Tests\DataListTest\ManyManyThroughSubEagerLoadObjectManyManyThroughSubSubEagerLoadObject; +use SilverStripe\ORM\Tests\DataListTest\BelongsManyManyEagerLoadObject; +use SilverStripe\ORM\Tests\DataListTest\BelongsManyManySubEagerLoadObject; +use SilverStripe\ORM\Tests\DataListTest\BelongsManyManySubSubEagerLoadObject; +use SilverStripe\ORM\Tests\DataListTest\MixedHasManyEagerLoadObject; +use SilverStripe\ORM\Tests\DataListTest\MixedHasOneEagerLoadObject; +use SilverStripe\ORM\Tests\DataListTest\MixedManyManyEagerLoadObject; class DataListTest extends SapphireTest { @@ -35,9 +60,37 @@ class DataListTest extends SapphireTest public static function getExtraDataObjects() { + $eagerLoadDataObjects = [ + EagerLoadObject::class, + HasOneEagerLoadObject::class, + HasOneSubEagerLoadObject::class, + HasOneSubSubEagerLoadObject::class, + BelongsToEagerLoadObject::class, + BelongsToSubEagerLoadObject::class, + BelongsToSubSubEagerLoadObject::class, + HasManyEagerLoadObject::class, + HasManySubEagerLoadObject::class, + HasManySubSubEagerLoadObject::class, + ManyManyEagerLoadObject::class, + ManyManySubEagerLoadObject::class, + ManyManySubSubEagerLoadObject::class, + ManyManyThroughEagerLoadObject::class, + ManyManyThroughSubEagerLoadObject::class, + ManyManyThroughSubSubEagerLoadObject::class, + EagerLoadObjectManyManyThroughEagerLoadObject::class, + ManyManyThroughEagerLoadObjectManyManyThroughSubEagerLoadObject::class, + ManyManyThroughSubEagerLoadObjectManyManyThroughSubSubEagerLoadObject::class, + BelongsManyManyEagerLoadObject::class, + BelongsManyManySubEagerLoadObject::class, + BelongsManyManySubSubEagerLoadObject::class, + MixedHasManyEagerLoadObject::class, + MixedHasOneEagerLoadObject::class, + MixedManyManyEagerLoadObject::class, + ]; return array_merge( DataObjectTest::$extra_data_objects, - ManyManyListTest::$extra_data_objects + ManyManyListTest::$extra_data_objects, + $eagerLoadDataObjects ); } @@ -2197,4 +2250,611 @@ class DataListTest extends SapphireTest $this->assertEmpty($expectedIDs, 'chunk returns all the results that the regular iterator does'); $this->assertEquals($expectedQueryCount, $dataQuery->getCount()); } + + /** + * @dataProvider provideEagerLoadRelations + */ + public function testEagerLoadRelations(string $iden, array $eagerLoad, int $expected): void + { + $this->createEagerLoadData(); + $dataList = EagerLoadObject::get()->eagerLoad(...$eagerLoad); + list($results, $selectCount) = $this->iterateEagerLoadData($dataList); + $this->assertSame($this->expectedEagerLoadData(), $results, $iden); + $this->assertSame($expected, $selectCount, $iden); + } + + public function provideEagerLoadRelations(): array + { + return [ + [ + 'iden' => 'lazy-load', + 'eagerLoad' => [], + 'expected' => 83 + ], + [ + 'iden' => 'has-one-a', + 'eagerLoad' => [ + 'HasOneEagerLoadObject', + ], + 'expected' => 82 + ], + [ + 'iden' => 'has-one-b', + 'eagerLoad' => [ + 'HasOneEagerLoadObject.HasOneSubEagerLoadObject', + ], + 'expected' => 81 + ], + [ + 'iden' => 'has-one-c', + 'eagerLoad' => [ + 'HasOneEagerLoadObject.HasOneSubEagerLoadObject.HasOneSubSubEagerLoadObject', + ], + 'expected' => 80 + ], + [ + 'iden' => 'belongs-to-a', + 'eagerLoad' => [ + 'BelongsToEagerLoadObject', + ], + 'expected' => 82 + ], + [ + 'iden' => 'belongs-to-b', + 'eagerLoad' => [ + 'BelongsToEagerLoadObject.BelongsToSubEagerLoadObject', + ], + 'expected' => 81 + ], + [ + 'iden' => 'belongs-to-c', + 'eagerLoad' => [ + 'BelongsToEagerLoadObject.BelongsToSubEagerLoadObject.BelongsToSubSubEagerLoadObject', + ], + 'expected' => 80 + ], + [ + 'iden' => 'has-many-a', + 'eagerLoad' => [ + 'HasManyEagerLoadObjects', + ], + 'expected' => 82 + ], + [ + 'iden' => 'has-many-b', + 'eagerLoad' => [ + 'HasManyEagerLoadObjects.HasManySubEagerLoadObjects', + ], + 'expected' => 79 + ], + [ + 'iden' => 'has-many-c', + 'eagerLoad' => [ + 'HasManyEagerLoadObjects.HasManySubEagerLoadObjects.HasManySubSubEagerLoadObjects', + ], + 'expected' => 72 + ], + [ + 'iden' => 'many-many-a', + 'eagerLoad' => [ + 'ManyManyEagerLoadObjects', + ], + 'expected' => 83 // same number as lazy-load, though without an INNER JOIN + ], + [ + 'iden' => 'many-many-b', + 'eagerLoad' => [ + 'ManyManyEagerLoadObjects.ManyManySubEagerLoadObjects', + ], + 'expected' => 81 + ], + [ + 'iden' => 'many-many-c', + 'eagerLoad' => [ + 'ManyManyEagerLoadObjects.ManyManySubEagerLoadObjects.ManyManySubSubEagerLoadObjects', + ], + 'expected' => 75 + ], + [ + 'iden' => 'many-many-through-a', + 'eagerLoad' => [ + 'ManyManyThroughEagerLoadObjects', + ], + 'expected' => 83 + ], + [ + 'iden' => 'many-many-through-b', + 'eagerLoad' => [ + 'ManyManyThroughEagerLoadObjects.ManyManyThroughSubEagerLoadObjects', + ], + 'expected' => 81 + ], + [ + 'iden' => 'many-many-through-c', + 'eagerLoad' => [ + 'ManyManyThroughEagerLoadObjects.ManyManyThroughSubEagerLoadObjects.ManyManyThroughSubSubEagerLoadObjects', + ], + 'expected' => 75 + ], + [ + 'iden' => 'belongs-many-many-a', + 'eagerLoad' => [ + 'BelongsManyManyEagerLoadObjects', + ], + 'expected' => 83 + ], + [ + 'iden' => 'belongs-many-many-b', + 'eagerLoad' => [ + 'BelongsManyManyEagerLoadObjects.BelongsManyManySubEagerLoadObjects', + ], + 'expected' => 81 + ], + [ + 'iden' => 'belongs-many-many-c', + 'eagerLoad' => [ + 'BelongsManyManyEagerLoadObjects.BelongsManyManySubEagerLoadObjects.BelongsManyManySubSubEagerLoadObjects', + ], + 'expected' => 75 + ], + [ + 'iden' => 'mixed-a', + 'eagerLoad' => [ + 'MixedManyManyEagerLoadObjects', + ], + 'expected' => 83 + ], + [ + 'iden' => 'mixed-b', + 'eagerLoad' => [ + 'MixedManyManyEagerLoadObjects.MixedHasManyEagerLoadObjects', + ], + 'expected' => 80 + ], + [ + 'iden' => 'mixed-c', + 'eagerLoad' => [ + 'MixedManyManyEagerLoadObjects.MixedHasManyEagerLoadObjects.MixedHasOneEagerLoadObject', + ], + 'expected' => 73 + ], + [ + 'iden' => 'all', + 'eagerLoad' => [ + 'HasOneEagerLoadObject.HasOneSubEagerLoadObject.HasOneSubSubEagerLoadObject', + 'BelongsToEagerLoadObject.BelongsToSubEagerLoadObject.BelongsToSubSubEagerLoadObject', + 'HasManyEagerLoadObjects.HasManySubEagerLoadObjects.HasManySubSubEagerLoadObjects', + 'ManyManyEagerLoadObjects.ManyManySubEagerLoadObjects.ManyManySubSubEagerLoadObjects', + 'ManyManyThroughEagerLoadObjects.ManyManyThroughSubEagerLoadObjects.ManyManyThroughSubSubEagerLoadObjects', + 'BelongsManyManyEagerLoadObjects.BelongsManyManySubEagerLoadObjects.BelongsManyManySubSubEagerLoadObjects', + 'MixedManyManyEagerLoadObjects.MixedHasManyEagerLoadObjects.MixedHasOneEagerLoadObject', + ], + 'expected' => 32 + ], + ]; + } + + private function createEagerLoadData(): void + { + for ($i = 0; $i < 2; $i++) { + // base object + $obj = new EagerLoadObject(); + $obj->Title = "obj $i"; + $objID = $obj->write(); + // has_one + $hasOneObj = new HasOneEagerLoadObject(); + $hasOneObj->Title = "hasOneObj $i"; + $hasOneObjID = $hasOneObj->write(); + $obj->HasOneEagerLoadObjectID = $hasOneObjID; + $obj->write(); + $hasOneSubObj = new HasOneSubEagerLoadObject(); + $hasOneSubObj->Title = "hasOneSubObj $i"; + $hasOneSubObjID = $hasOneSubObj->write(); + $hasOneObj->HasOneSubEagerLoadObjectID = $hasOneSubObjID; + $hasOneObj->write(); + $hasOneSubSubObj = new HasOneSubSubEagerLoadObject(); + $hasOneSubSubObj->Title = "hasOneSubSubObj $i"; + $hasOneSubSubObjID = $hasOneSubSubObj->write(); + $hasOneSubObj->HasOneSubSubEagerLoadObjectID = $hasOneSubSubObjID; + $hasOneSubObj->write(); + // belongs_to + $belongsToObj = new BelongsToEagerLoadObject(); + $belongsToObj->EagerLoadObjectID = $objID; + $belongsToObj->Title = "belongsToObj $i"; + $belongsToObjID = $belongsToObj->write(); + $belongsToSubObj = new BelongsToSubEagerLoadObject(); + $belongsToSubObj->BelongsToEagerLoadObjectID = $belongsToObjID; + $belongsToSubObj->Title = "belongsToSubObj $i"; + $belongsToSubObjID = $belongsToSubObj->write(); + $belongsToSubSubObj = new BelongsToSubSubEagerLoadObject(); + $belongsToSubSubObj->BelongsToSubEagerLoadObjectID = $belongsToSubObjID; + $belongsToSubSubObj->Title = "belongsToSubSubObj $i"; + $belongsToSubSubObj->write(); + // has_many + for ($j = 0; $j < 2; $j++) { + $hasManyObj = new HasManyEagerLoadObject(); + $hasManyObj->EagerLoadObjectID = $objID; + $hasManyObj->Title = "hasManyObj $i $j"; + $hasManyObjID = $hasManyObj->write(); + for ($k = 0; $k < 2; $k++) { + $hasManySubObj = new HasManySubEagerLoadObject(); + $hasManySubObj->HasManyEagerLoadObjectID = $hasManyObjID; + $hasManySubObj->Title = "hasManySubObj $i $j $k"; + $hasManySubObjID = $hasManySubObj->write(); + for ($l = 0; $l < 2; $l++) { + $hasManySubSubObj = new HasManySubSubEagerLoadObject(); + $hasManySubSubObj->HasManySubEagerLoadObjectID = $hasManySubObjID; + $hasManySubSubObj->Title = "hasManySubSubObj $i $j $k $l"; + $hasManySubSubObj->write(); + } + } + } + // many_many + for ($j = 0; $j < 2; $j++) { + $manyManyObj = new ManyManyEagerLoadObject(); + $manyManyObj->Title = "manyManyObj $i $j"; + $manyManyObj->write(); + $obj->ManyManyEagerLoadObjects()->add($manyManyObj); + for ($k = 0; $k < 2; $k++) { + $manyManySubObj = new ManyManySubEagerLoadObject(); + $manyManySubObj->Title = "manyManySubObj $i $j $k"; + $manyManySubObj->write(); + $manyManyObj->ManyManySubEagerLoadObjects()->add($manyManySubObj); + for ($l = 0; $l < 2; $l++) { + $manyManySubSubObj = new ManyManySubSubEagerLoadObject(); + $manyManySubSubObj->Title = "manyManySubSubObj $i $j $k $l"; + $manyManySubSubObj->write(); + $manyManySubObj->ManyManySubSubEagerLoadObjects()->add($manyManySubSubObj); + } + } + } + // many_many_through + for ($j = 0; $j < 2; $j++) { + $manyManyThroughObj = new ManyManyThroughEagerLoadObject(); + $manyManyThroughObj->Title = "manyManyThroughObj $i $j"; + $manyManyThroughObj->write(); + $obj->ManyManyThroughEagerLoadObjects()->add($manyManyThroughObj); + for ($k = 0; $k < 2; $k++) { + $manyManyThroughSubObj = new ManyManyThroughSubEagerLoadObject(); + $manyManyThroughSubObj->Title = "manyManyThroughSubObj $i $j $k"; + $manyManyThroughSubObj->write(); + $manyManyThroughObj->ManyManyThroughSubEagerLoadObjects()->add($manyManyThroughSubObj); + for ($l = 0; $l < 2; $l++) { + $manyManyThroughSubSubObj = new ManyManyThroughSubSubEagerLoadObject(); + $manyManyThroughSubSubObj->Title = "manyManyThroughSubSubObj $i $j $k $l"; + $manyManyThroughSubSubObj->write(); + $manyManyThroughSubObj->ManyManyThroughSubSubEagerLoadObjects()->add($manyManyThroughSubSubObj); + } + } + } + // belongs_many_many + for ($j = 0; $j < 2; $j++) { + $belongsManyManyObj = new BelongsManyManyEagerLoadObject(); + $belongsManyManyObj->Title = "belongsManyManyObj $i $j"; + $belongsManyManyObj->write(); + $obj->BelongsManyManyEagerLoadObjects()->add($belongsManyManyObj); + for ($k = 0; $k < 2; $k++) { + $belongsManyManySubObj = new BelongsManyManySubEagerLoadObject(); + $belongsManyManySubObj->Title = "belongsManyManySubObj $i $j $k"; + $belongsManyManySubObj->write(); + $belongsManyManyObj->BelongsManyManySubEagerLoadObjects()->add($belongsManyManySubObj); + for ($l = 0; $l < 2; $l++) { + $belongsManyManySubSubObj = new BelongsManyManySubSubEagerLoadObject(); + $belongsManyManySubSubObj->Title = "belongsManyManySubSubObj $i $j $k $l"; + $belongsManyManySubSubObj->write(); + $belongsManyManySubObj->BelongsManyManySubSubEagerLoadObjects()->add($belongsManyManySubSubObj); + } + } + } + // mixed + for ($j = 0; $j < 2; $j++) { + $mixedManyManyObj = new MixedManyManyEagerLoadObject(); + $mixedManyManyObj->Title = "mixedManyManyObj $i $j"; + $mixedManyManyObj->write(); + $obj->MixedManyManyEagerLoadObjects()->add($mixedManyManyObj); + for ($k = 0; $k < 2; $k++) { + $mixedHasManyObj = new MixedHasManyEagerLoadObject(); + $mixedHasManyObj->Title = "mixedHasManyObj $i $j $k"; + $mixedHasManyObjID = $mixedHasManyObj->write(); + $mixedManyManyObj->MixedHasManyEagerLoadObjects()->add($mixedHasManyObj); + for ($l = 0; $l < 2; $l++) { + $mixedHasOneObj = new MixedHasOneEagerLoadObject(); + $mixedHasOneObj->Title = "mixedHasOneObj $i $j $k $l"; + $mixedHasOneObjID = $mixedHasOneObj->write(); + $mixedHasManyObj->MixedHasOneEagerLoadObjectID = $mixedHasOneObjID; + $mixedHasManyObj->write(); + } + } + } + } + } + + private function iterateEagerLoadData(DataList $dataList): array + { + $results = []; + $selectCount = -1; + $showqueries = $_REQUEST['showqueries'] ?? null; + try { + // force showqueries on to count the number of SELECT statements via output-buffering + // if this approach turns out to be too brittle later on, switch to what debugbar + // does and use tractorcow/proxy-db which should be installed as a dev-dependency + // https://github.com/lekoala/silverstripe-debugbar/blob/master/code/Collector/DatabaseCollector.php#L79 + $_REQUEST['showqueries'] = 1; + ob_start(); + echo '__START_ITERATE__'; + $results = []; + $i = 0; + foreach ($dataList as $obj) { + // base obj + $results[] = $obj->Title; + // has_one + $hasOneObj = $obj->HasOneEagerLoadObject(); + $hasOneSubObj = $hasOneObj->HasOneSubEagerLoadObject(); + $hasOneSubSubObj = $hasOneSubObj->HasOneSubSubEagerLoadObject(); + $results[] = $hasOneObj->Title; + $results[] = $hasOneSubObj->Title; + $results[] = $hasOneSubSubObj->Title; + // belongs_to + $belongsToObj = $obj->BelongsToEagerLoadObject(); + $belongsToSubObj = $belongsToObj->BelongsToSubEagerLoadObject(); + $belongsToSubSubObj = $belongsToSubObj->BelongsToSubSubEagerLoadObject(); + $results[] = $belongsToObj->Title; + $results[] = $belongsToSubObj->Title; + $results[] = $belongsToSubSubObj->Title; + // has_many + foreach ($obj->HasManyEagerLoadObjects() as $hasManyObj) { + $results[] = $hasManyObj->Title; + foreach ($hasManyObj->HasManySubEagerLoadObjects() as $hasManySubObj) { + $results[] = $hasManySubObj->Title; + foreach ($hasManySubObj->HasManySubSubEagerLoadObjects() as $hasManySubSubObj) { + $results[] = $hasManySubSubObj->Title; + } + } + } + // many_many + foreach ($obj->ManyManyEagerLoadObjects() as $manyManyObj) { + $results[] = $manyManyObj->Title; + foreach ($manyManyObj->ManyManySubEagerLoadObjects() as $manyManySubObj) { + $results[] = $manyManySubObj->Title; + foreach ($manyManySubObj->ManyManySubSubEagerLoadObjects() as $manyManySubSubObj) { + $results[] = $manyManySubSubObj->Title; + } + } + } + // many_many_through + foreach ($obj->ManyManyThroughEagerLoadObjects() as $manyManyThroughObj) { + $results[] = $manyManyThroughObj->Title; + foreach ($manyManyThroughObj->ManyManyThroughSubEagerLoadObjects() as $manyManyThroughSubObj) { + $results[] = $manyManyThroughSubObj->Title; + foreach ($manyManyThroughSubObj->ManyManyThroughSubSubEagerLoadObjects() as $manyManyThroughSubSubObj) { + $results[] = $manyManyThroughSubSubObj->Title; + } + } + } + // belongs_many_many + foreach ($obj->BelongsManyManyEagerLoadObjects() as $belongsManyManyObj) { + $results[] = $belongsManyManyObj->Title; + foreach ($belongsManyManyObj->BelongsManyManySubEagerLoadObjects() as $belongsManyManySubObj) { + $results[] = $belongsManyManySubObj->Title; + foreach ($belongsManyManySubObj->BelongsManyManySubSubEagerLoadObjects() as $belongsManyManySubSubObj) { + $results[] = $belongsManyManySubSubObj->Title; + } + } + } + // mixed + foreach ($obj->MixedManyManyEagerLoadObjects() as $mixedManyManyObj) { + $results[] = $mixedManyManyObj->Title; + foreach ($mixedManyManyObj->MixedHasManyEagerLoadObjects() as $mixedHasManyObj) { + $results[] = $mixedHasManyObj->Title; + $results[] = $mixedHasManyObj->MixedHasOneEagerLoadObject()->Title; + } + } + } + $s = ob_get_clean(); + $s = preg_replace('/.*__START_ITERATE__/s', '', $s); + $selectCount = substr_count($s, ': SELECT'); + } finally { + if ($showqueries) { + $_REQUEST['showqueries'] = $showqueries; + } else { + unset($_REQUEST['showqueries']); + } + } + return [$results, $selectCount]; + } + + private function expectedEagerLoadData(): array + { + return [ + 'obj 0', + 'hasOneObj 0', + 'hasOneSubObj 0', + 'hasOneSubSubObj 0', + 'belongsToObj 0', + 'belongsToSubObj 0', + 'belongsToSubSubObj 0', + 'hasManyObj 0 0', + 'hasManySubObj 0 0 0', + 'hasManySubSubObj 0 0 0 0', + 'hasManySubSubObj 0 0 0 1', + 'hasManySubObj 0 0 1', + 'hasManySubSubObj 0 0 1 0', + 'hasManySubSubObj 0 0 1 1', + 'hasManyObj 0 1', + 'hasManySubObj 0 1 0', + 'hasManySubSubObj 0 1 0 0', + 'hasManySubSubObj 0 1 0 1', + 'hasManySubObj 0 1 1', + 'hasManySubSubObj 0 1 1 0', + 'hasManySubSubObj 0 1 1 1', + 'manyManyObj 0 0', + 'manyManySubObj 0 0 0', + 'manyManySubSubObj 0 0 0 0', + 'manyManySubSubObj 0 0 0 1', + 'manyManySubObj 0 0 1', + 'manyManySubSubObj 0 0 1 0', + 'manyManySubSubObj 0 0 1 1', + 'manyManyObj 0 1', + 'manyManySubObj 0 1 0', + 'manyManySubSubObj 0 1 0 0', + 'manyManySubSubObj 0 1 0 1', + 'manyManySubObj 0 1 1', + 'manyManySubSubObj 0 1 1 0', + 'manyManySubSubObj 0 1 1 1', + 'manyManyThroughObj 0 0', + 'manyManyThroughSubObj 0 0 0', + 'manyManyThroughSubSubObj 0 0 0 0', + 'manyManyThroughSubSubObj 0 0 0 1', + 'manyManyThroughSubObj 0 0 1', + 'manyManyThroughSubSubObj 0 0 1 0', + 'manyManyThroughSubSubObj 0 0 1 1', + 'manyManyThroughObj 0 1', + 'manyManyThroughSubObj 0 1 0', + 'manyManyThroughSubSubObj 0 1 0 0', + 'manyManyThroughSubSubObj 0 1 0 1', + 'manyManyThroughSubObj 0 1 1', + 'manyManyThroughSubSubObj 0 1 1 0', + 'manyManyThroughSubSubObj 0 1 1 1', + 'belongsManyManyObj 0 0', + 'belongsManyManySubObj 0 0 0', + 'belongsManyManySubSubObj 0 0 0 0', + 'belongsManyManySubSubObj 0 0 0 1', + 'belongsManyManySubObj 0 0 1', + 'belongsManyManySubSubObj 0 0 1 0', + 'belongsManyManySubSubObj 0 0 1 1', + 'belongsManyManyObj 0 1', + 'belongsManyManySubObj 0 1 0', + 'belongsManyManySubSubObj 0 1 0 0', + 'belongsManyManySubSubObj 0 1 0 1', + 'belongsManyManySubObj 0 1 1', + 'belongsManyManySubSubObj 0 1 1 0', + 'belongsManyManySubSubObj 0 1 1 1', + 'mixedManyManyObj 0 0', + 'mixedHasManyObj 0 0 0', + 'mixedHasOneObj 0 0 0 1', + 'mixedHasManyObj 0 0 1', + 'mixedHasOneObj 0 0 1 1', + 'mixedManyManyObj 0 1', + 'mixedHasManyObj 0 1 0', + 'mixedHasOneObj 0 1 0 1', + 'mixedHasManyObj 0 1 1', + 'mixedHasOneObj 0 1 1 1', + 'obj 1', + 'hasOneObj 1', + 'hasOneSubObj 1', + 'hasOneSubSubObj 1', + 'belongsToObj 1', + 'belongsToSubObj 1', + 'belongsToSubSubObj 1', + 'hasManyObj 1 0', + 'hasManySubObj 1 0 0', + 'hasManySubSubObj 1 0 0 0', + 'hasManySubSubObj 1 0 0 1', + 'hasManySubObj 1 0 1', + 'hasManySubSubObj 1 0 1 0', + 'hasManySubSubObj 1 0 1 1', + 'hasManyObj 1 1', + 'hasManySubObj 1 1 0', + 'hasManySubSubObj 1 1 0 0', + 'hasManySubSubObj 1 1 0 1', + 'hasManySubObj 1 1 1', + 'hasManySubSubObj 1 1 1 0', + 'hasManySubSubObj 1 1 1 1', + 'manyManyObj 1 0', + 'manyManySubObj 1 0 0', + 'manyManySubSubObj 1 0 0 0', + 'manyManySubSubObj 1 0 0 1', + 'manyManySubObj 1 0 1', + 'manyManySubSubObj 1 0 1 0', + 'manyManySubSubObj 1 0 1 1', + 'manyManyObj 1 1', + 'manyManySubObj 1 1 0', + 'manyManySubSubObj 1 1 0 0', + 'manyManySubSubObj 1 1 0 1', + 'manyManySubObj 1 1 1', + 'manyManySubSubObj 1 1 1 0', + 'manyManySubSubObj 1 1 1 1', + 'manyManyThroughObj 1 0', + 'manyManyThroughSubObj 1 0 0', + 'manyManyThroughSubSubObj 1 0 0 0', + 'manyManyThroughSubSubObj 1 0 0 1', + 'manyManyThroughSubObj 1 0 1', + 'manyManyThroughSubSubObj 1 0 1 0', + 'manyManyThroughSubSubObj 1 0 1 1', + 'manyManyThroughObj 1 1', + 'manyManyThroughSubObj 1 1 0', + 'manyManyThroughSubSubObj 1 1 0 0', + 'manyManyThroughSubSubObj 1 1 0 1', + 'manyManyThroughSubObj 1 1 1', + 'manyManyThroughSubSubObj 1 1 1 0', + 'manyManyThroughSubSubObj 1 1 1 1', + 'belongsManyManyObj 1 0', + 'belongsManyManySubObj 1 0 0', + 'belongsManyManySubSubObj 1 0 0 0', + 'belongsManyManySubSubObj 1 0 0 1', + 'belongsManyManySubObj 1 0 1', + 'belongsManyManySubSubObj 1 0 1 0', + 'belongsManyManySubSubObj 1 0 1 1', + 'belongsManyManyObj 1 1', + 'belongsManyManySubObj 1 1 0', + 'belongsManyManySubSubObj 1 1 0 0', + 'belongsManyManySubSubObj 1 1 0 1', + 'belongsManyManySubObj 1 1 1', + 'belongsManyManySubSubObj 1 1 1 0', + 'belongsManyManySubSubObj 1 1 1 1', + 'mixedManyManyObj 1 0', + 'mixedHasManyObj 1 0 0', + 'mixedHasOneObj 1 0 0 1', + 'mixedHasManyObj 1 0 1', + 'mixedHasOneObj 1 0 1 1', + 'mixedManyManyObj 1 1', + 'mixedHasManyObj 1 1 0', + 'mixedHasOneObj 1 1 0 1', + 'mixedHasManyObj 1 1 1', + 'mixedHasOneObj 1 1 1 1', + ]; + } + + public function testEagerLoadFourthLevelException(): void + { + $eagerLoadRelation = implode('.', [ + 'MixedManyManyEagerLoadObjects', + 'MixedHasManyEagerLoadObjects', + 'MixedHasOneEagerLoadObject', + 'FourthLevel' + ]); + $expectedMessage = implode(' - ', [ + 'Eager loading only supports up to 3 levels of nesting, passed 4 levels', + $eagerLoadRelation + ]); + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage($expectedMessage); + EagerLoadObject::get()->eagerLoad($eagerLoadRelation); + } + + /** + * @dataProvider provideEagerLoadInvalidRelationException + */ + public function testEagerLoadInvalidRelationException(string $eagerLoadRelation): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage("Invalid relation passed to eagerLoad() - $eagerLoadRelation"); + $this->createEagerLoadData(); + EagerLoadObject::get()->eagerLoad($eagerLoadRelation)->toArray(); + } + + public function provideEagerLoadInvalidRelationException(): array + { + return [ + [ + 'Invalid', + ], + [ + 'MixedManyManyEagerLoadObjects.Invalid', + ], + [ + 'MixedManyManyEagerLoadObjects.MixedHasManyEagerLoadObjects.Invalid' + ] + ]; + } } diff --git a/tests/php/ORM/DataListTest/BelongsManyManyEagerLoadObject.php b/tests/php/ORM/DataListTest/BelongsManyManyEagerLoadObject.php new file mode 100644 index 000000000..d48783928 --- /dev/null +++ b/tests/php/ORM/DataListTest/BelongsManyManyEagerLoadObject.php @@ -0,0 +1,23 @@ + 'Varchar' + ]; + + private static $many_many = [ + 'EagerLoadObjects' => EagerLoadObject::class + ]; + + private static $belongs_many_many = [ + 'BelongsManyManySubEagerLoadObjects' => BelongsManyManySubEagerLoadObject::class + ]; +} diff --git a/tests/php/ORM/DataListTest/BelongsManyManySubEagerLoadObject.php b/tests/php/ORM/DataListTest/BelongsManyManySubEagerLoadObject.php new file mode 100644 index 000000000..43856f7b0 --- /dev/null +++ b/tests/php/ORM/DataListTest/BelongsManyManySubEagerLoadObject.php @@ -0,0 +1,26 @@ + 'Varchar' + ]; + + private static $many_many = [ + // note: need to use singular name for relationship rather than plural otherwise + // will get error get + // mysqli_sql_exception: Incorrect table name 'BelongsManyManySubEagerLoadObject_BelongsManyManyEagerLoadObjects' + 'BelongsManyManyEagerLoadObject' => BelongsManyManyEagerLoadObject::class + ]; + + private static $belongs_many_many = [ + 'BelongsManyManySubSubEagerLoadObjects' => BelongsManyManySubSubEagerLoadObject::class + ]; +} diff --git a/tests/php/ORM/DataListTest/BelongsManyManySubSubEagerLoadObject.php b/tests/php/ORM/DataListTest/BelongsManyManySubSubEagerLoadObject.php new file mode 100644 index 000000000..3df24f93a --- /dev/null +++ b/tests/php/ORM/DataListTest/BelongsManyManySubSubEagerLoadObject.php @@ -0,0 +1,24 @@ + 'Varchar' + ]; + + private static $many_many = [ + // note: using 'MyManyMany' as name to otherise will get + // mysqli_sql_exception: Incorrect table name'BelongsManyManySubSubEagerLoadObject_BelongsManyManySubEagerLoadObjects' + // or 'BelongsManyManySubSubEagerLoadObject_BelongsManyManySubEagerLoadObjects' + // for relationships named BelongsManyManySubEagerLoadObjects (plural) and + // BelongsManyManySubEagerLoadObject (singular) + 'MyManyMany' => BelongsManyManySubEagerLoadObject::class + ]; +} diff --git a/tests/php/ORM/DataListTest/BelongsToEagerLoadObject.php b/tests/php/ORM/DataListTest/BelongsToEagerLoadObject.php new file mode 100644 index 000000000..688b166d7 --- /dev/null +++ b/tests/php/ORM/DataListTest/BelongsToEagerLoadObject.php @@ -0,0 +1,23 @@ + 'Varchar' + ]; + + private static $has_one = [ + 'EagerLoadObject' => EagerLoadObject::class + ]; + + private static $belongs_to = [ + 'BelongsToSubEagerLoadObject' => BelongsToSubEagerLoadObject::class + ]; +} diff --git a/tests/php/ORM/DataListTest/BelongsToSubEagerLoadObject.php b/tests/php/ORM/DataListTest/BelongsToSubEagerLoadObject.php new file mode 100644 index 000000000..c89f59c49 --- /dev/null +++ b/tests/php/ORM/DataListTest/BelongsToSubEagerLoadObject.php @@ -0,0 +1,23 @@ + 'Varchar' + ]; + + private static $has_one = [ + 'BelongsToEagerLoadObject' => BelongsToEagerLoadObject::class + ]; + + private static $belongs_to = [ + 'BelongsToSubSubEagerLoadObject' => BelongsToSubSubEagerLoadObject::class + ]; +} diff --git a/tests/php/ORM/DataListTest/BelongsToSubSubEagerLoadObject.php b/tests/php/ORM/DataListTest/BelongsToSubSubEagerLoadObject.php new file mode 100644 index 000000000..d0cca6c3e --- /dev/null +++ b/tests/php/ORM/DataListTest/BelongsToSubSubEagerLoadObject.php @@ -0,0 +1,19 @@ + 'Varchar' + ]; + + private static $has_one = [ + 'BelongsToSubEagerLoadObject' => BelongsToSubEagerLoadObject::class + ]; +} diff --git a/tests/php/ORM/DataListTest/EagerLoadObject.php b/tests/php/ORM/DataListTest/EagerLoadObject.php new file mode 100644 index 000000000..7577c23f3 --- /dev/null +++ b/tests/php/ORM/DataListTest/EagerLoadObject.php @@ -0,0 +1,41 @@ + 'Varchar' + ]; + + private static $has_one = [ + 'HasOneEagerLoadObject' => HasOneEagerLoadObject::class + ]; + + private static $belongs_to = [ + 'BelongsToEagerLoadObject' => BelongsToEagerLoadObject::class + ]; + + private static $has_many = [ + 'HasManyEagerLoadObjects' => HasManyEagerLoadObject::class + ]; + + private static $many_many = [ + 'ManyManyEagerLoadObjects' => ManyManyEagerLoadObject::class, + 'ManyManyThroughEagerLoadObjects' => [ + 'through' => EagerLoadObjectManyManyThroughEagerLoadObject::class, + 'from' => 'EagerLoadObject', + 'to' => 'ManyManyThroughEagerLoadObject', + ], + 'MixedManyManyEagerLoadObjects' => MixedManyManyEagerLoadObject::class, + ]; + + private static $belongs_many_many = [ + 'BelongsManyManyEagerLoadObjects' => BelongsManyManyEagerLoadObject::class, + ]; +} diff --git a/tests/php/ORM/DataListTest/EagerLoadObjectManyManyThroughEagerLoadObject.php b/tests/php/ORM/DataListTest/EagerLoadObjectManyManyThroughEagerLoadObject.php new file mode 100644 index 000000000..83c41372f --- /dev/null +++ b/tests/php/ORM/DataListTest/EagerLoadObjectManyManyThroughEagerLoadObject.php @@ -0,0 +1,20 @@ + 'Varchar' + ]; + + private static $has_one = [ + 'EagerLoadObject' => EagerLoadObject::class, + 'ManyManyThroughEagerLoadObject' => ManyManyThroughEagerLoadObject::class + ]; +} diff --git a/tests/php/ORM/DataListTest/HasManyEagerLoadObject.php b/tests/php/ORM/DataListTest/HasManyEagerLoadObject.php new file mode 100644 index 000000000..ed8358c99 --- /dev/null +++ b/tests/php/ORM/DataListTest/HasManyEagerLoadObject.php @@ -0,0 +1,23 @@ + 'Varchar' + ]; + + private static $has_one = [ + 'EagerLoadObject' => EagerLoadObject::class + ]; + + private static $has_many = [ + 'HasManySubEagerLoadObjects' => HasManySubEagerLoadObject::class + ]; +} diff --git a/tests/php/ORM/DataListTest/HasManySubEagerLoadObject.php b/tests/php/ORM/DataListTest/HasManySubEagerLoadObject.php new file mode 100644 index 000000000..94fde53cb --- /dev/null +++ b/tests/php/ORM/DataListTest/HasManySubEagerLoadObject.php @@ -0,0 +1,23 @@ + 'Varchar' + ]; + + private static $has_one = [ + 'HasManyEagerLoadObject' => HasManyEagerLoadObject::class + ]; + + private static $has_many = [ + 'HasManySubSubEagerLoadObjects' => HasManySubSubEagerLoadObject::class + ]; +} diff --git a/tests/php/ORM/DataListTest/HasManySubSubEagerLoadObject.php b/tests/php/ORM/DataListTest/HasManySubSubEagerLoadObject.php new file mode 100644 index 000000000..2ec5e4b7e --- /dev/null +++ b/tests/php/ORM/DataListTest/HasManySubSubEagerLoadObject.php @@ -0,0 +1,23 @@ + 'Varchar' + ]; + + private static $has_one = [ + 'HasManySubEagerLoadObject' => HasManySubEagerLoadObject::class + ]; + + private static $has_many = [ + 'HasManySubSubSubEagerLoadObjects' => HasManySubSubSubEagerLoadObject::class + ]; +} diff --git a/tests/php/ORM/DataListTest/HasOneEagerLoadObject.php b/tests/php/ORM/DataListTest/HasOneEagerLoadObject.php new file mode 100644 index 000000000..7ff426669 --- /dev/null +++ b/tests/php/ORM/DataListTest/HasOneEagerLoadObject.php @@ -0,0 +1,23 @@ + 'Varchar' + ]; + + private static $has_one = [ + 'HasOneSubEagerLoadObject' => HasOneSubEagerLoadObject::class + ]; + + private static $belongs_to = [ + 'EagerLoadObject' => EagerLoadObject::class + ]; +} diff --git a/tests/php/ORM/DataListTest/HasOneSubEagerLoadObject.php b/tests/php/ORM/DataListTest/HasOneSubEagerLoadObject.php new file mode 100644 index 000000000..0657287d4 --- /dev/null +++ b/tests/php/ORM/DataListTest/HasOneSubEagerLoadObject.php @@ -0,0 +1,23 @@ + 'Varchar' + ]; + + private static $has_one = [ + 'HasOneSubSubEagerLoadObject' => HasOneSubSubEagerLoadObject::class + ]; + + private static $belongs_to = [ + 'HasOneEagerLoadObject' => HasOneEagerLoadObject::class + ]; +} diff --git a/tests/php/ORM/DataListTest/HasOneSubSubEagerLoadObject.php b/tests/php/ORM/DataListTest/HasOneSubSubEagerLoadObject.php new file mode 100644 index 000000000..1d9d1ad1e --- /dev/null +++ b/tests/php/ORM/DataListTest/HasOneSubSubEagerLoadObject.php @@ -0,0 +1,19 @@ + 'Varchar' + ]; + + private static $belongs_to = [ + 'HasOneSubEagerLoadObject' => HasOneSubEagerLoadObject::class + ]; +} diff --git a/tests/php/ORM/DataListTest/ManyManyEagerLoadObject.php b/tests/php/ORM/DataListTest/ManyManyEagerLoadObject.php new file mode 100644 index 000000000..55c82a77b --- /dev/null +++ b/tests/php/ORM/DataListTest/ManyManyEagerLoadObject.php @@ -0,0 +1,23 @@ + 'Varchar' + ]; + + private static $many_many = [ + 'ManyManySubEagerLoadObjects' => ManyManySubEagerLoadObject::class + ]; + + private static $belongs_many_many = [ + 'EagerLoadObjects' => EagerLoadObject::class + ]; +} diff --git a/tests/php/ORM/DataListTest/ManyManySubEagerLoadObject.php b/tests/php/ORM/DataListTest/ManyManySubEagerLoadObject.php new file mode 100644 index 000000000..ff4621ba3 --- /dev/null +++ b/tests/php/ORM/DataListTest/ManyManySubEagerLoadObject.php @@ -0,0 +1,24 @@ + 'Varchar' + ]; + + private static $many_many = [ + 'ManyManySubSubEagerLoadObjects' => ManyManySubSubEagerLoadObject::class + ]; + + private static $belongs_many_many = [ + 'EagerLoadObjects' => EagerLoadObject::class, + 'ManyManyEagerLoadObjects' => ManyManyEagerLoadObject::class + ]; +} diff --git a/tests/php/ORM/DataListTest/ManyManySubSubEagerLoadObject.php b/tests/php/ORM/DataListTest/ManyManySubSubEagerLoadObject.php new file mode 100644 index 000000000..e6874277d --- /dev/null +++ b/tests/php/ORM/DataListTest/ManyManySubSubEagerLoadObject.php @@ -0,0 +1,20 @@ + 'Varchar' + ]; + + private static $belongs_many_many = [ + 'ManyManyEagerLoadObjects' => ManyManyEagerLoadObjects::class, + 'ManyManySubEagerLoadObjects' => ManyManySubEagerLoadObject::class + ]; +} diff --git a/tests/php/ORM/DataListTest/ManyManyThroughEagerLoadObject.php b/tests/php/ORM/DataListTest/ManyManyThroughEagerLoadObject.php new file mode 100644 index 000000000..ee10b2e21 --- /dev/null +++ b/tests/php/ORM/DataListTest/ManyManyThroughEagerLoadObject.php @@ -0,0 +1,27 @@ + 'Varchar' + ]; + + private static $many_many = [ + 'ManyManyThroughSubEagerLoadObjects' => [ + 'through' => ManyManyThroughEagerLoadObjectManyManyThroughSubEagerLoadObject::class, + 'from' => 'ManyManyThroughEagerLoadObject', + 'to' => 'ManyManyThroughSubEagerLoadObject', + ] + ]; + + private static $belongs_many_many = [ + 'EagerLoadObjects' => EagerLoadObject::class + ]; +} diff --git a/tests/php/ORM/DataListTest/ManyManyThroughEagerLoadObjectManyManyThroughSubEagerLoadObject.php b/tests/php/ORM/DataListTest/ManyManyThroughEagerLoadObjectManyManyThroughSubEagerLoadObject.php new file mode 100644 index 000000000..b4cdaf71e --- /dev/null +++ b/tests/php/ORM/DataListTest/ManyManyThroughEagerLoadObjectManyManyThroughSubEagerLoadObject.php @@ -0,0 +1,20 @@ + 'Varchar' + ]; + + private static $has_one = [ + 'ManyManyThroughEagerLoadObject' => ManyManyThroughEagerLoadObject::class, + 'ManyManyThroughSubEagerLoadObject' => ManyManyThroughSubEagerLoadObject::class + ]; +} diff --git a/tests/php/ORM/DataListTest/ManyManyThroughSubEagerLoadObject.php b/tests/php/ORM/DataListTest/ManyManyThroughSubEagerLoadObject.php new file mode 100644 index 000000000..c0f9c72b4 --- /dev/null +++ b/tests/php/ORM/DataListTest/ManyManyThroughSubEagerLoadObject.php @@ -0,0 +1,27 @@ + 'Varchar' + ]; + + private static $many_many = [ + 'ManyManyThroughSubSubEagerLoadObjects' => [ + 'through' => ManyManyThroughSubEagerLoadObjectManyManyThroughSubSubEagerLoadObject::class, + 'from' => 'ManyManyThroughSubEagerLoadObject', + 'to' => 'ManyManyThroughSubSubEagerLoadObject', + ] + ]; + + private static $belongs_many_many = [ + 'ManyManyThroughEagerLoadObjects' => ManyManyThroughEagerLoadObject::class + ]; +} diff --git a/tests/php/ORM/DataListTest/ManyManyThroughSubEagerLoadObjectManyManyThroughSubSubEagerLoadObject.php b/tests/php/ORM/DataListTest/ManyManyThroughSubEagerLoadObjectManyManyThroughSubSubEagerLoadObject.php new file mode 100644 index 000000000..f48b733be --- /dev/null +++ b/tests/php/ORM/DataListTest/ManyManyThroughSubEagerLoadObjectManyManyThroughSubSubEagerLoadObject.php @@ -0,0 +1,21 @@ + 'Varchar' + ]; + + private static $has_one = [ + 'ManyManyThroughSubEagerLoadObject' => ManyManyThroughSubEagerLoadObject::class, + 'ManyManyThroughSubSubEagerLoadObject' => ManyManyThroughSubSubEagerLoadObject::class + ]; +} diff --git a/tests/php/ORM/DataListTest/ManyManyThroughSubSubEagerLoadObject.php b/tests/php/ORM/DataListTest/ManyManyThroughSubSubEagerLoadObject.php new file mode 100644 index 000000000..85607b78d --- /dev/null +++ b/tests/php/ORM/DataListTest/ManyManyThroughSubSubEagerLoadObject.php @@ -0,0 +1,19 @@ + 'Varchar' + ]; + + private static $belongs_many_many = [ + 'ManyManyThroughSubEagerLoadObjects' => ManyManyThroughSubEagerLoadObject::class + ]; +} diff --git a/tests/php/ORM/DataListTest/MixedHasManyEagerLoadObject.php b/tests/php/ORM/DataListTest/MixedHasManyEagerLoadObject.php new file mode 100644 index 000000000..bf84638b0 --- /dev/null +++ b/tests/php/ORM/DataListTest/MixedHasManyEagerLoadObject.php @@ -0,0 +1,20 @@ + 'Varchar' + ]; + + private static $has_one = [ + 'MixedManyManyEagerLoadObject' => MixedManyManyEagerLoadObject::class, + 'MixedHasOneEagerLoadObject' => MixedHasOneEagerLoadObject::class + ]; +} diff --git a/tests/php/ORM/DataListTest/MixedHasOneEagerLoadObject.php b/tests/php/ORM/DataListTest/MixedHasOneEagerLoadObject.php new file mode 100644 index 000000000..eda0d404d --- /dev/null +++ b/tests/php/ORM/DataListTest/MixedHasOneEagerLoadObject.php @@ -0,0 +1,20 @@ + 'Varchar' + ]; + + private static $has_one = [ + // this is used for a "four levels deep" test + 'FourthLevel' => MixedHasOneEagerLoadObject::class + ]; +} diff --git a/tests/php/ORM/DataListTest/MixedManyManyEagerLoadObject.php b/tests/php/ORM/DataListTest/MixedManyManyEagerLoadObject.php new file mode 100644 index 000000000..cdabc0f81 --- /dev/null +++ b/tests/php/ORM/DataListTest/MixedManyManyEagerLoadObject.php @@ -0,0 +1,23 @@ + 'Varchar' + ]; + + private static $has_many = [ + 'MixedHasManyEagerLoadObjects' => MixedHasManyEagerLoadObject::class + ]; + + private static $belongs_many_many = [ + 'EagerLoadObjects' => EagerLoadObject::class, + ]; +}