diff --git a/src/Illuminate/Database/Eloquent/Builder.php b/src/Illuminate/Database/Eloquent/Builder.php index 4ea4ab3e1992..5afa8373b420 100755 --- a/src/Illuminate/Database/Eloquent/Builder.php +++ b/src/Illuminate/Database/Eloquent/Builder.php @@ -523,7 +523,9 @@ public function where($column, $operator = null, $value = null, $boolean = 'and' } else { - call_user_func_array(array($this->query, 'where'), func_get_args()); + $args = func_get_args(); + $args[0] = $this->model->unaliasColumn($column); + call_user_func_array(array($this->query, 'where'), $args); } return $this; diff --git a/src/Illuminate/Database/Eloquent/Model.php b/src/Illuminate/Database/Eloquent/Model.php index 6d9eef937d68..715bb953fc66 100755 --- a/src/Illuminate/Database/Eloquent/Model.php +++ b/src/Illuminate/Database/Eloquent/Model.php @@ -83,6 +83,13 @@ abstract class Model implements ArrayAccess, Arrayable, Jsonable, JsonSerializab */ protected $original = array(); + /** + * The model's column-to-alias mappings. + * + * @var array + */ + protected $aliases = array(); + /** * The loaded relationships for the model. * @@ -1435,7 +1442,7 @@ public function update(array $attributes = array()) { if ( ! $this->exists) { - return $this->newQuery()->update($attributes); + return $this->newQuery()->update($this->unaliasAttributes($attributes)); } return $this->fill($attributes)->save(); @@ -1557,7 +1564,7 @@ protected function performUpdate(Builder $query, array $options = []) if (count($dirty) > 0) { - $this->setKeysForSaveQuery($query)->update($dirty); + $this->setKeysForSaveQuery($query)->update($this->unaliasAttributes($dirty)); $this->fireModelEvent('updated', false); } @@ -1588,7 +1595,7 @@ protected function performInsert(Builder $query, array $options = []) // If the model has an incrementing key, we can use the "insertGetId" method on // the query builder, which will give us back the final inserted ID for this // table from the database. Not all tables have to be incrementing though. - $attributes = $this->attributes; + $attributes = $this->unaliasAttributes($this->attributes); if ($this->incrementing) { @@ -2087,6 +2094,91 @@ public function getForeignKey() return snake_case(class_basename($this)).'_id'; } + /** + * Set the column alias mappings. + * + * @param array $aliases + * @return void + */ + public function setAliases(array $aliases) + { + $this->aliases = $aliases; + } + + /** + * Apply aliases to any columns that have aliases defined. + * + * @param array $attributes + * @return array + */ + public function aliasAttributes($attributes) + { + forEach ( $attributes as $key => $value ) + { + $aliased = $this->aliasColumn($key); + if ( $aliased !== $key ) + { + $attributes[$aliased] = $value; + unset($attributes[$key]); + } + } + return $attributes; + } + + /** + * Replace any aliases in the array's keys with the actual column names. + * + * @param array $attributes + * @return array + */ + public function unaliasAttributes($attributes) + { + forEach ( $attributes as $key => $value ) + { + $unaliased = $this->unaliasColumn($key); + if ( $unaliased !== $key ) + { + $attributes[$unaliased] = $value; + unset($attributes[$key]); + } + } + return $attributes; + } + + /** + * If the column has an alias defined, return the alias, otherwise return + * the original column. + * + * @param string $column + * @return string + */ + public function aliasColumn($column) + { + if ( array_key_exists($column, $this->aliases) ) + { + return $this->aliases[$column]; + } + return $column; + } + + /** + * If the provided key is defined as an alias, return the original column, + * otherwise return the key unchanged. + * + * @param string $key + * @return string + */ + public function unaliasColumn($key) + { + $unaliased = array_search($key, $this->aliases); + if ( $unaliased ) + { + return $unaliased; + } + + return $key; + } + /** * Get the hidden attributes for the model. * @@ -2941,7 +3033,7 @@ public function getAttributes() */ public function setRawAttributes(array $attributes, $sync = false) { - $this->attributes = $attributes; + $this->attributes = $this->aliasAttributes($attributes); if ($sync) $this->syncOriginal(); } diff --git a/tests/Database/DatabaseEloquentBuilderTest.php b/tests/Database/DatabaseEloquentBuilderTest.php index 01d3a955d741..c925c262598e 100755 --- a/tests/Database/DatabaseEloquentBuilderTest.php +++ b/tests/Database/DatabaseEloquentBuilderTest.php @@ -279,7 +279,6 @@ public function testRelationshipEagerLoadProcess() public function testGetRelationProperlySetsNestedRelationships() { $builder = $this->getBuilder(); - $builder->setModel($this->getMockModel()); $builder->getModel()->shouldReceive('orders')->once()->andReturn($relation = m::mock('stdClass')); $relationQuery = m::mock('stdClass'); $relation->shouldReceive('getQuery')->andReturn($relationQuery); @@ -293,7 +292,6 @@ public function testGetRelationProperlySetsNestedRelationships() public function testGetRelationProperlySetsNestedRelationshipsWithSimilarNames() { $builder = $this->getBuilder(); - $builder->setModel($this->getMockModel()); $builder->getModel()->shouldReceive('orders')->once()->andReturn($relation = m::mock('stdClass')); $builder->getModel()->shouldReceive('ordersGroups')->once()->andReturn($groupsRelation = m::mock('stdClass')); @@ -410,11 +408,21 @@ public function testRealNestedWhereWithScopes() public function testSimpleWhere() { $builder = $this->getBuilder(); + $builder->getModel()->shouldReceive('unaliasColumn')->with('foo')->andReturn('foo'); $builder->getQuery()->shouldReceive('where')->once()->with('foo', '=', 'bar'); $result = $builder->where('foo', '=', 'bar'); $this->assertEquals($result, $builder); } + public function testWhereWithAlias() + { + $builder = $this->getBuilder(); + $builder->getModel()->shouldReceive('unaliasColumn')->with('bar')->andReturn('foo'); + $builder->getQuery()->shouldReceive('where')->once()->with('foo', '=', 'baz'); + $result = $builder->where('bar', '=', 'baz'); + $this->assertEquals($result, $builder); + } + public function testDeleteOverride() { @@ -490,7 +498,9 @@ protected function mockConnectionForModel($model, $database) protected function getBuilder() { - return new Builder($this->getMockQueryBuilder()); + $builder = new Builder($this->getMockQueryBuilder()); + $builder->setModel($this->getMockModel()); + return $builder; } diff --git a/tests/Database/DatabaseEloquentModelTest.php b/tests/Database/DatabaseEloquentModelTest.php index c26a1651a3af..6a95288617cc 100755 --- a/tests/Database/DatabaseEloquentModelTest.php +++ b/tests/Database/DatabaseEloquentModelTest.php @@ -168,6 +168,30 @@ public function testUpdateProcess() } + public function testUpdateWithAliases() + { + $model = $this->getMock('EloquentModelStub', array('newQueryWithoutScopes', 'updateTimestamps')); + $model->setAliases(['foo' => 'bar']); + $query = m::mock('Illuminate\Database\Eloquent\Builder'); + $query->shouldReceive('where')->once()->with('id', '=', 1); + $query->shouldReceive('update')->once()->with(array('name' => 'taylor', 'foo' => 'baz')); + $model->expects($this->once())->method('newQueryWithoutScopes')->will($this->returnValue($query)); + $model->expects($this->once())->method('updateTimestamps'); + $model->setEventDispatcher($events = m::mock('Illuminate\Contracts\Events\Dispatcher')); + $events->shouldReceive('until')->once()->with('eloquent.saving: '.get_class($model), $model)->andReturn(true); + $events->shouldReceive('until')->once()->with('eloquent.updating: '.get_class($model), $model)->andReturn(true); + $events->shouldReceive('fire')->once()->with('eloquent.updated: '.get_class($model), $model)->andReturn(true); + $events->shouldReceive('fire')->once()->with('eloquent.saved: '.get_class($model), $model)->andReturn(true); + + $model->id = 1; + $model->syncOriginal(); + $model->name = 'taylor'; + $model->bar = 'baz'; + $model->exists = true; + $this->assertTrue($model->save()); + } + + public function testUpdateProcessDoesntOverrideTimestamps() { $model = $this->getMock('EloquentModelStub', array('newQueryWithoutScopes')); @@ -342,6 +366,51 @@ public function testTimestampsAreCreatedFromStringsAndIntegers() } + public function testInsertWithAliases() + { + $model = $this->getMock('EloquentModelStub', array('newQueryWithoutScopes', 'updateTimestamps')); + $model->setAliases(['foo' => 'bar']); + $query = m::mock('Illuminate\Database\Eloquent\Builder'); + $query->shouldReceive('insertGetId')->once()->with(array('name' => 'taylor', 'foo' => 'baz'), 'id')->andReturn(1); + $model->expects($this->once())->method('newQueryWithoutScopes')->will($this->returnValue($query)); + $model->expects($this->once())->method('updateTimestamps'); + + $model->setEventDispatcher($events = m::mock('Illuminate\Contracts\Events\Dispatcher')); + $events->shouldReceive('until')->once()->with('eloquent.saving: '.get_class($model), $model)->andReturn(true); + $events->shouldReceive('until')->once()->with('eloquent.creating: '.get_class($model), $model)->andReturn(true); + $events->shouldReceive('fire')->once()->with('eloquent.created: '.get_class($model), $model); + $events->shouldReceive('fire')->once()->with('eloquent.saved: '.get_class($model), $model); + + $model->name = 'taylor'; + $model->bar = 'baz'; + $model->exists = false; + $this->assertTrue($model->save()); + $this->assertEquals(1, $model->id); + $this->assertTrue($model->exists); + + $model = $this->getMock('EloquentModelStub', array('newQueryWithoutScopes', 'updateTimestamps')); + $model->setAliases(['foo' => 'bar']); + $query = m::mock('Illuminate\Database\Eloquent\Builder'); + $query->shouldReceive('insert')->once()->with(array('name' => 'taylor', 'foo' => 'baz')); + $model->expects($this->once())->method('newQueryWithoutScopes')->will($this->returnValue($query)); + $model->expects($this->once())->method('updateTimestamps'); + $model->setIncrementing(false); + + $model->setEventDispatcher($events = m::mock('Illuminate\Contracts\Events\Dispatcher')); + $events->shouldReceive('until')->once()->with('eloquent.saving: '.get_class($model), $model)->andReturn(true); + $events->shouldReceive('until')->once()->with('eloquent.creating: '.get_class($model), $model)->andReturn(true); + $events->shouldReceive('fire')->once()->with('eloquent.created: '.get_class($model), $model); + $events->shouldReceive('fire')->once()->with('eloquent.saved: '.get_class($model), $model); + + $model->name = 'taylor'; + $model->bar = 'baz'; + $model->exists = false; + $this->assertTrue($model->save()); + $this->assertNull($model->id); + $this->assertTrue($model->exists); + } + + public function testInsertProcess() { $model = $this->getMock('EloquentModelStub', array('newQueryWithoutScopes', 'updateTimestamps'));