Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions lib/Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -1687,13 +1687,11 @@ public static function from(string $from): Relation
}

/**
* @param string|array<string|mixed> $include
*
* @return Relation<static>
*/
public static function include(string|array $include): Relation
public static function includes(): Relation
{
return static::Relation()->include($include);
return static::Relation()->includes(...func_get_args());
}

/**
Expand Down
32 changes: 29 additions & 3 deletions lib/Relation.php
Original file line number Diff line number Diff line change
Expand Up @@ -247,13 +247,39 @@ public function from(string $from): Relation
}

/**
* @param string|array<string|mixed> $include
* The purpose of includes is to solve N+1 problems in relational situations.
* Let's say you have an Author, and authors write many Books. You would
* ordinarily need to execute one query (+1) to retrieve the Author,
* then one query for each of his books (N).
*
* You can avoid this problem by specifying relationships to be included in the
* result set. For example:
*
* $users = User::includes('address');
* foreach($users as $user) {
* $user->address->city
* }
*
* ...allows you to access the address attribute of the User model without
* firing an additional query. This will often result in a performance
* improvement over a simple join. You can also specify multiple
* relationships, like this:
*
* $users = User::includes('address', 'friends');
*
* Loading nested relationships is possible using a Hash:
*
* $users = User::includes(
* 'address',
* 'friends' => ['address', 'followers']
* )
*
* @return Relation<TModel>
*/
public function include(string|array $include): Relation
public function includes(): Relation
{
$this->options['include'] = $include;
$includes = static::toSingleArg(...func_get_args());
$this->options['include'] = $includes;

return $this;
}
Expand Down
42 changes: 42 additions & 0 deletions test/ActiveRecordIncludesTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

namespace test;

use test\models\Venue;

class ActiveRecordIncludesTest extends \DatabaseTestCase
{
public function testSimpleString(): void
{
$venue = Venue::includes('events')
->first();

$this->assertEquals('Monday Night Music Club feat. The Shivers', $venue->events[0]->title);
}

public function testChainSimpleString(): void
{
$venue = Venue::includes('events')
->includes('hosts')
->first();

$this->assertEquals('Monday Night Music Club feat. The Shivers', $venue->events[0]->title);
$this->assertEquals('David Letterman', $venue->hosts[0]->name);
}

public function testArrayOfSimpleStrings(): void
{
$venue = Venue::includes(['events', 'hosts'])
->first();
$this->assertEquals('Monday Night Music Club feat. The Shivers', $venue->events[0]->title);
$this->assertEquals('David Letterman', $venue->hosts[0]->name);
}

public function testListOfSimpleStrings(): void
{
$venue = Venue::includes('events', 'hosts')
->first();
$this->assertEquals('Monday Night Music Club feat. The Shivers', $venue->events[0]->title);
$this->assertEquals('David Letterman', $venue->hosts[0]->name);
}
}
8 changes: 4 additions & 4 deletions test/HasManyThroughTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public function testHasManyThrough()

public function testHasManyThroughInclude()
{
$user = User::include([
$user = User::includes([
'user_newsletters'
])->find(1);

Expand All @@ -37,16 +37,16 @@ public function testHasManyThroughInclude()

public function testHasManyThroughIncludeEager()
{
$venue = Venue::include('events')->find(1);
$venue = Venue::includes('events')->find(1);
$this->assertEquals(1, $venue->events[0]->id);

$venue = Venue::include('hosts')->find(1);
$venue = Venue::includes('hosts')->find(1);
$this->assertEquals(1, $venue->hosts[0]->id);
}

public function testHasManyThoughIncludeEagerWithNamespace()
{
$user = User::include('newsletters')->find(1);
$user = User::includes('newsletters')->find(1);

$this->assertEquals(1, $user->id);
$this->assertEquals(1, $user->newsletters[0]->id);
Expand Down
26 changes: 13 additions & 13 deletions test/RelationshipTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ public function testHasManyBasic()
public function testEagerLoadingThreeLevelsDeep()
{
/* Before fix Undefined offset: 0 */
$venue = Venue::include([
$venue = Venue::includes([
'events'=>[
'host'=>[
'events'
Expand Down Expand Up @@ -706,7 +706,7 @@ public function testEagerLoadingRespectsAssociationOptions()
]
]
];
$venues = Venue::include('events')->find([2, 6]);
$venues = Venue::includes('events')->find([2, 6]);

$this->assert_sql_has('WHERE (length(title) = ?) AND (venue_id IN(?,?)) ORDER BY id asc', ActiveRecord\Table::load(Event::class)->last_sql);
$this->assertEquals(1, count($venues[0]->events));
Expand All @@ -724,13 +724,13 @@ public function testEagerLoadingWithThrough()
'order' => 'hosts.id asc'
]
];
$venues = Venue::include('hosts')->find([2, 6]);
$venues = Venue::includes('hosts')->find([2, 6]);
$this->assertEquals(2, count($venues));
}

public function testEagerLoadingHasManyX()
{
$venues = Venue::include('events')->find([2, 6]);
$venues = Venue::includes('events')->find([2, 6]);
$this->assert_sql_has('WHERE venue_id IN(?,?)', ActiveRecord\Table::load(Event::class)->last_sql);

foreach ($venues[0]->events as $event) {
Expand All @@ -742,7 +742,7 @@ public function testEagerLoadingHasManyX()

public function testEagerLoadingHasManyWithNoRelatedRows()
{
$venues = Venue::include('events')->find([7, 8]);
$venues = Venue::includes('events')->find([7, 8]);

foreach ($venues as $v) {
$this->assertTrue(empty($v->events));
Expand All @@ -758,7 +758,7 @@ public function testEagerLoadingHasManyArrayOfIncludes()
'books' => true,
'awesome_people' => true
];
$authors = Author::include(['books', 'awesome_people'])
$authors = Author::includes(['books', 'awesome_people'])
->find([1, 2]);

$assocs = ['books', 'awesome_people'];
Expand All @@ -782,7 +782,7 @@ public function testEagerLoadingHasManyArrayOfIncludes()

public function testEagerLoadingHasManyNested()
{
$venues = Venue::include(['events' => ['host']])
$venues = Venue::includes(['events' => ['host']])
->find([1, 2]);

$this->assertEquals(2, count($venues));
Expand All @@ -803,7 +803,7 @@ public function testEagerLoadingHasManyNested()

public function testEagerLoadingBelongsTo()
{
$events = Event::include('venue')
$events = Event::includes('venue')
->find([1, 2, 3, 5, 7]);

foreach ($events as $event) {
Expand All @@ -815,7 +815,7 @@ public function testEagerLoadingBelongsTo()

public function testEagerLoadingBelongsToArrayOfIncludes()
{
$events = Event::include(['venue', 'host'])->find([1, 2, 3, 5, 7]);
$events = Event::includes(['venue', 'host'])->find([1, 2, 3, 5, 7]);

foreach ($events as $event) {
$this->assertEquals($event->venue_id, $event->venue->id);
Expand All @@ -836,7 +836,7 @@ public function testEagerLoadingBelongsToNested()
'author' => true
];

$books = Book::include([
$books = Book::includes([
'author' => [
'awesome_people'
]
Expand All @@ -857,7 +857,7 @@ public function testEagerLoadingBelongsToWithNoRelatedRows()
$e1 = Event::create(['venue_id' => 200, 'host_id' => 200, 'title' => 'blah', 'type' => 'Music']);
$e2 = Event::create(['venue_id' => 200, 'host_id' => 200, 'title' => 'blah2', 'type' => 'Music']);

$events = Event::include('venue')
$events = Event::includes('venue')
->find([$e1->id, $e2->id]);

foreach ($events as $e) {
Expand All @@ -870,7 +870,7 @@ public function testEagerLoadingBelongsToWithNoRelatedRows()

public function testEagerLoadingClonesRelatedObjects()
{
$events = Event::include('venue')
$events = Event::includes('venue')
->find([2, 3]);

$venue = $events[0]->venue;
Expand All @@ -883,7 +883,7 @@ public function testEagerLoadingClonesRelatedObjects()

public function testEagerLoadingClonesNestedRelatedObjects()
{
$venues = Venue::include(['events' => ['host']])
$venues = Venue::includes(['events' => ['host']])
->find([1, 2, 6, 9]);

$unchanged_host = $venues[2]->events[0]->host;
Expand Down
4 changes: 2 additions & 2 deletions test/phpstan/ModelToRelation.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ public function where(): Relation
/**
* @return Relation<Book>
*/
public function include(): Relation
public function includes(): Relation
{
return Book::include([]);
return Book::includes([]);
}
}