Skip to content

Commit 4b9c9a4

Browse files
[12.x] Add flag to disable where clauses for withAttributes method on Eloquent Builder (#55199)
* feat: add pending attributes to withAttributes method on Eloquent relationships * feat: add ability to disable where clauses when calling withAttributes * formatting * update tests --------- Co-authored-by: Taylor Otwell <[email protected]>
1 parent 543867d commit 4b9c9a4

File tree

4 files changed

+722
-3
lines changed

4 files changed

+722
-3
lines changed

src/Illuminate/Database/Eloquent/Builder.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1819,16 +1819,19 @@ protected function addNestedWiths($name, $results)
18191819
*
18201820
* @param \Illuminate\Contracts\Database\Query\Expression|array|string $attributes
18211821
* @param mixed $value
1822+
* @param bool $asConditions
18221823
* @return $this
18231824
*/
1824-
public function withAttributes(Expression|array|string $attributes, $value = null)
1825+
public function withAttributes(Expression|array|string $attributes, $value = null, $asConditions = true)
18251826
{
18261827
if (! is_array($attributes)) {
18271828
$attributes = [$attributes => $value];
18281829
}
18291830

1830-
foreach ($attributes as $column => $value) {
1831-
$this->where($this->qualifyColumn($column), $value);
1831+
if ($asConditions) {
1832+
foreach ($attributes as $column => $value) {
1833+
$this->where($this->qualifyColumn($column), $value);
1834+
}
18321835
}
18331836

18341837
$this->pendingAttributes = array_merge($this->pendingAttributes, $attributes);
Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
<?php
2+
3+
namespace Illuminate\Tests\Database;
4+
5+
use Illuminate\Database\Capsule\Manager as DB;
6+
use Illuminate\Database\Eloquent\Model;
7+
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
8+
use Illuminate\Database\Eloquent\Relations\MorphToMany;
9+
use PHPUnit\Framework\TestCase;
10+
11+
class DatabaseEloquentBelongsToManyWithAttributesPendingTest extends TestCase
12+
{
13+
protected function setUp(): void
14+
{
15+
$db = new DB;
16+
17+
$db->addConnection([
18+
'driver' => 'sqlite',
19+
'database' => ':memory:',
20+
]);
21+
$db->bootEloquent();
22+
$db->setAsGlobal();
23+
$this->createSchema();
24+
}
25+
26+
public function testCreatesPendingAttributesAndPivotValues(): void
27+
{
28+
$post = ManyToManyPendingAttributesPost::create();
29+
$tag = $post->metaTags()->create(['name' => 'long article']);
30+
31+
$this->assertSame('long article', $tag->name);
32+
$this->assertTrue($tag->visible);
33+
34+
$pivot = DB::table('pending_attributes_pivot')->first();
35+
$this->assertSame('meta', $pivot->type);
36+
$this->assertSame($post->id, $pivot->post_id);
37+
$this->assertSame($tag->id, $pivot->tag_id);
38+
}
39+
40+
public function testQueriesPendingAttributesAndPivotValues(): void
41+
{
42+
$post = new ManyToManyPendingAttributesPost(['id' => 2]);
43+
$wheres = $post->metaTags()->toBase()->wheres;
44+
45+
$this->assertContains([
46+
'type' => 'Basic',
47+
'column' => 'pending_attributes_pivot.tag_id',
48+
'operator' => '=',
49+
'value' => 2,
50+
'boolean' => 'and',
51+
], $wheres);
52+
53+
$this->assertContains([
54+
'type' => 'Basic',
55+
'column' => 'pending_attributes_pivot.type',
56+
'operator' => '=',
57+
'value' => 'meta',
58+
'boolean' => 'and',
59+
], $wheres);
60+
61+
// Ensure no other wheres exist
62+
$this->assertCount(2, $wheres);
63+
}
64+
65+
public function testMorphToManyPendingAttributes(): void
66+
{
67+
$post = new ManyToManyPendingAttributesPost(['id' => 2]);
68+
$wheres = $post->morphedTags()->toBase()->wheres;
69+
70+
$this->assertContains([
71+
'type' => 'Basic',
72+
'column' => 'pending_attributes_taggables.type',
73+
'operator' => '=',
74+
'value' => 'meta',
75+
'boolean' => 'and',
76+
], $wheres);
77+
78+
$this->assertContains([
79+
'type' => 'Basic',
80+
'column' => 'pending_attributes_taggables.taggable_type',
81+
'operator' => '=',
82+
'value' => ManyToManyPendingAttributesPost::class,
83+
'boolean' => 'and',
84+
], $wheres);
85+
86+
$this->assertContains([
87+
'type' => 'Basic',
88+
'column' => 'pending_attributes_taggables.taggable_id',
89+
'operator' => '=',
90+
'value' => 2,
91+
'boolean' => 'and',
92+
], $wheres);
93+
94+
// Ensure no other wheres exist
95+
$this->assertCount(3, $wheres);
96+
97+
$tag = $post->morphedTags()->create(['name' => 'new tag']);
98+
99+
$this->assertTrue($tag->visible);
100+
$this->assertSame('new tag', $tag->name);
101+
$this->assertSame($tag->id, $post->morphedTags()->first()->id);
102+
}
103+
104+
public function testMorphedByManyPendingAttributes(): void
105+
{
106+
$tag = new ManyToManyPendingAttributesTag(['id' => 4]);
107+
$wheres = $tag->morphedPosts()->toBase()->wheres;
108+
109+
$this->assertContains([
110+
'type' => 'Basic',
111+
'column' => 'pending_attributes_taggables.type',
112+
'operator' => '=',
113+
'value' => 'meta',
114+
'boolean' => 'and',
115+
], $wheres);
116+
117+
$this->assertContains([
118+
'type' => 'Basic',
119+
'column' => 'pending_attributes_taggables.taggable_type',
120+
'operator' => '=',
121+
'value' => ManyToManyPendingAttributesPost::class,
122+
'boolean' => 'and',
123+
], $wheres);
124+
125+
$this->assertContains([
126+
'type' => 'Basic',
127+
'column' => 'pending_attributes_taggables.tag_id',
128+
'operator' => '=',
129+
'value' => 4,
130+
'boolean' => 'and',
131+
], $wheres);
132+
133+
// Ensure no other wheres exist
134+
$this->assertCount(3, $wheres);
135+
136+
$post = $tag->morphedPosts()->create();
137+
$this->assertSame('Title!', $post->title);
138+
$this->assertSame($post->id, $tag->morphedPosts()->first()->id);
139+
}
140+
141+
protected function createSchema()
142+
{
143+
$this->schema()->create('pending_attributes_posts', function ($table) {
144+
$table->increments('id');
145+
$table->string('title')->nullable();
146+
$table->timestamps();
147+
});
148+
149+
$this->schema()->create('pending_attributes_tags', function ($table) {
150+
$table->increments('id');
151+
$table->string('name');
152+
$table->boolean('visible')->nullable();
153+
$table->timestamps();
154+
});
155+
156+
$this->schema()->create('pending_attributes_pivot', function ($table) {
157+
$table->integer('post_id');
158+
$table->integer('tag_id');
159+
$table->string('type');
160+
});
161+
162+
$this->schema()->create('pending_attributes_taggables', function ($table) {
163+
$table->integer('tag_id');
164+
$table->integer('taggable_id');
165+
$table->string('taggable_type');
166+
$table->string('type');
167+
});
168+
}
169+
170+
/**
171+
* Tear down the database schema.
172+
*
173+
* @return void
174+
*/
175+
protected function tearDown(): void
176+
{
177+
$this->schema()->drop('pending_attributes_posts');
178+
$this->schema()->drop('pending_attributes_tags');
179+
$this->schema()->drop('pending_attributes_pivot');
180+
}
181+
182+
/**
183+
* Get a database connection instance.
184+
*
185+
* @return \Illuminate\Database\Connection
186+
*/
187+
protected function connection($connection = 'default')
188+
{
189+
return Model::getConnectionResolver()->connection($connection);
190+
}
191+
192+
/**
193+
* Get a schema builder instance.
194+
*
195+
* @return \Illuminate\Database\Schema\Builder
196+
*/
197+
protected function schema($connection = 'default')
198+
{
199+
return $this->connection($connection)->getSchemaBuilder();
200+
}
201+
}
202+
203+
class ManyToManyPendingAttributesPost extends Model
204+
{
205+
protected $guarded = [];
206+
protected $table = 'pending_attributes_posts';
207+
208+
public function tags(): BelongsToMany
209+
{
210+
return $this->belongsToMany(
211+
ManyToManyPendingAttributesTag::class,
212+
'pending_attributes_pivot',
213+
'tag_id',
214+
'post_id',
215+
);
216+
}
217+
218+
public function metaTags(): BelongsToMany
219+
{
220+
return $this->tags()
221+
->withAttributes('visible', true, asConditions: false)
222+
->withPivotValue('type', 'meta');
223+
}
224+
225+
public function morphedTags(): MorphToMany
226+
{
227+
return $this
228+
->morphToMany(
229+
ManyToManyPendingAttributesTag::class,
230+
'taggable',
231+
'pending_attributes_taggables',
232+
relatedPivotKey: 'tag_id'
233+
)
234+
->withAttributes('visible', true, asConditions: false)
235+
->withPivotValue('type', 'meta');
236+
}
237+
}
238+
239+
class ManyToManyPendingAttributesTag extends Model
240+
{
241+
protected $guarded = [];
242+
protected $table = 'pending_attributes_tags';
243+
244+
public function morphedPosts(): MorphToMany
245+
{
246+
return $this
247+
->morphedByMany(
248+
ManyToManyPendingAttributesPost::class,
249+
'taggable',
250+
'pending_attributes_taggables',
251+
'tag_id',
252+
)
253+
->withAttributes('title', 'Title!', asConditions: false)
254+
->withPivotValue('type', 'meta');
255+
}
256+
}

0 commit comments

Comments
 (0)