Skip to content

Commit 44a9fcf

Browse files
marcotisitaylorotwell
authored andcommitted
[5.1] Fix wrong PDO bindings when using whereHas() (#15723)
* Fix wrong PDO bindings when using whereHas() * Fix coding style
1 parent 45455d7 commit 44a9fcf

File tree

2 files changed

+246
-3
lines changed

2 files changed

+246
-3
lines changed

src/Illuminate/Database/Eloquent/Builder.php

+3-3
Original file line numberDiff line numberDiff line change
@@ -788,9 +788,9 @@ protected function mergeWheresToHas(Builder $hasQuery, Relation $relation)
788788

789789
$hasQuery = $hasQuery->getModel()->removeGlobalScopes($hasQuery);
790790

791-
$hasQuery->mergeWheres(
792-
$relationQuery->wheres, $relationQuery->getBindings()
793-
);
791+
$whereBindings = array_get($relationQuery->getRawBindings(), 'where', []);
792+
793+
$hasQuery->mergeWheres($relationQuery->wheres, $whereBindings);
794794

795795
$this->query->addBinding($hasQuery->getQuery()->getBindings(), 'where');
796796
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
<?php
2+
3+
use Illuminate\Database\Capsule\Manager as DB;
4+
use Illuminate\Database\Eloquent\Builder;
5+
use Illuminate\Database\Eloquent\Model as Eloquent;
6+
use Illuminate\Database\Eloquent\ScopeInterface;
7+
8+
class DatabaseCountBindingsTest extends PHPUnit_Framework_TestCase
9+
{
10+
/**
11+
* Setup the database schema.
12+
* @return void
13+
*/
14+
public function setUp()
15+
{
16+
$db = new DB;
17+
18+
$db->addConnection([
19+
'driver' => 'sqlite',
20+
'database' => ':memory:',
21+
]);
22+
23+
$db->bootEloquent();
24+
$db->setAsGlobal();
25+
26+
$this->createSchema();
27+
}
28+
29+
/**
30+
* Tear down the database schema.
31+
* @return void
32+
*/
33+
public function tearDown()
34+
{
35+
$this->schema()->drop('pages');
36+
$this->schema()->drop('page_translations');
37+
$this->schema()->drop('blocks');
38+
$this->schema()->drop('block_translations');
39+
}
40+
41+
public function testBindingCount()
42+
{
43+
Page::create(['id' => 1, 'uri' => 'test1']);
44+
PageTranslation::create(['id' => 1, 'page_id' => 1, 'locale' => 'en', 'content' => 'Lorem ipsum dolor sit amet']);
45+
Block::create(['id' => 1, 'page_id' => 1, 'type' => 'static']);
46+
BlockTranslation::create(['id' => 1, 'block_id' => 1, 'locale' => 'en', 'content' => 'Lorem ipsum dolor sit amet']);
47+
48+
$pagesWithStaticBlocks = Page::with('blocks')->whereHas('blocks', function ($query) {
49+
$query->where('type', 'static');
50+
});
51+
52+
$this->assertEquals(1, $pagesWithStaticBlocks->get()->count());
53+
54+
Page::addGlobalScope(new TestTranslationScope());
55+
$this->assertTrue(Page::hasGlobalScope(new TestTranslationScope()));
56+
57+
Block::addGlobalScope(new TestTranslationScope());
58+
$this->assertTrue(Block::hasGlobalScope(new TestTranslationScope()));
59+
60+
$pagesWithStaticBlocks = Page::with('blocks')->whereHas('blocks', function ($query) {
61+
$query->where('type', 'static');
62+
});
63+
64+
$questionMarksCount = substr_count($pagesWithStaticBlocks->toSql(), '?');
65+
66+
$bindingsCount = count($pagesWithStaticBlocks->getBindings());
67+
68+
$this->assertEquals($questionMarksCount, $bindingsCount);
69+
}
70+
71+
protected function createSchema()
72+
{
73+
$this->schema()->create('pages', function ($table) {
74+
$table->increments('id');
75+
$table->string('uri');
76+
$table->timestamps();
77+
});
78+
79+
$this->schema()->create('page_translations', function ($table) {
80+
$table->increments('id');
81+
$table->integer('page_id');
82+
$table->string('locale');
83+
$table->text('content');
84+
$table->timestamps();
85+
});
86+
87+
$this->schema()->create('blocks', function ($table) {
88+
$table->increments('id');
89+
$table->integer('page_id');
90+
$table->string('type');
91+
$table->timestamps();
92+
});
93+
94+
$this->schema()->create('block_translations', function ($table) {
95+
$table->increments('id');
96+
$table->integer('block_id');
97+
$table->string('locale');
98+
$table->text('content');
99+
$table->timestamps();
100+
});
101+
}
102+
103+
/**
104+
* Helpers...
105+
*/
106+
107+
/**
108+
* Get a database connection instance.
109+
* @return \Illuminate\Database\Connection
110+
*/
111+
protected function connection($connection = 'default')
112+
{
113+
return Eloquent::getConnectionResolver()->connection($connection);
114+
}
115+
116+
/**
117+
* Get a schema builder instance.
118+
* @return \Illuminate\Database\Schema\Builder
119+
*/
120+
protected function schema($connection = 'default')
121+
{
122+
return $this->connection($connection)->getSchemaBuilder();
123+
}
124+
}
125+
126+
/**
127+
* Eloquent Models...
128+
*/
129+
class Page extends Eloquent
130+
{
131+
protected $table = 'pages';
132+
133+
protected $guarded = [];
134+
135+
public function blocks()
136+
{
137+
return $this->hasMany('Block', 'page_id');
138+
}
139+
140+
public function translations()
141+
{
142+
return $this->hasMany('PageTranslation', 'page_id');
143+
}
144+
}
145+
146+
class PageTranslation extends Eloquent
147+
{
148+
protected $table = 'page_translations';
149+
150+
protected $guarded = [];
151+
152+
public function page()
153+
{
154+
return $this->belongsTo('Page', 'page_id');
155+
}
156+
}
157+
158+
class Block extends Eloquent
159+
{
160+
protected $table = 'blocks';
161+
162+
protected $guarded = [];
163+
164+
public function page()
165+
{
166+
return $this->belongsTo('Page', 'page_id');
167+
}
168+
169+
public function translations()
170+
{
171+
return $this->hasMany('BlockTranslation', 'block_id');
172+
}
173+
}
174+
175+
class BlockTranslation extends Eloquent
176+
{
177+
protected $table = 'block_translations';
178+
179+
protected $guarded = [];
180+
181+
public function block()
182+
{
183+
return $this->belongsTo('Block', 'block_id');
184+
}
185+
}
186+
187+
class TestTranslationScope implements ScopeInterface
188+
{
189+
/**
190+
* This holds the index of the new join binding.
191+
* @var int
192+
*/
193+
protected $bindingIndex;
194+
195+
/**
196+
* Apply the scope to a given Eloquent query builder.
197+
*
198+
* @param \Illuminate\Database\Eloquent\Builder $builder
199+
* @param \Illuminate\Database\Eloquent\Model $model
200+
*
201+
* @return void
202+
*/
203+
public function apply(Builder $builder, Eloquent $model)
204+
{
205+
$translationTable = $model->translations()->getModel()->getTable();
206+
$foreignKey = $model->getForeignKey();
207+
208+
$this->bindingIndex = count($builder->getQuery()->getRawBindings()['join']);
209+
210+
$builder->leftJoin($translationTable, function ($join) use ($translationTable, $foreignKey, $model) {
211+
$join->on($translationTable.'.'.$foreignKey, '=', $model->getQualifiedKeyName())
212+
->where($translationTable.'.locale', '=', 'en');
213+
});
214+
}
215+
216+
/**
217+
* Remove the scope from the given Eloquent query builder.
218+
*
219+
* @param \Illuminate\Database\Eloquent\Builder $builder
220+
* @param \Illuminate\Database\Eloquent\Model $model
221+
*
222+
* @return void
223+
*/
224+
public function remove(Builder $builder, Eloquent $model)
225+
{
226+
$query = $builder->getQuery();
227+
228+
$bindings = $query->getRawBindings()['join'];
229+
230+
unset($bindings[$this->bindingIndex]);
231+
$bindings = array_values($bindings);
232+
$query->setBindings($bindings, 'join');
233+
234+
$translationTable = $model->translations()->getModel()->getTable();
235+
236+
$query->joins = collect($query->joins)
237+
->reject(function ($join) use ($translationTable) {
238+
return $join->table == $translationTable;
239+
})
240+
->values()
241+
->all();
242+
}
243+
}

0 commit comments

Comments
 (0)