Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add column aliasing option in Eloquent Model #8200

Closed
wants to merge 4 commits into from
Closed
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
4 changes: 3 additions & 1 deletion src/Illuminate/Database/Eloquent/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
100 changes: 96 additions & 4 deletions src/Illuminate/Database/Eloquent/Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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();
}
Expand Down
16 changes: 13 additions & 3 deletions tests/Database/DatabaseEloquentBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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'));

Expand Down Expand Up @@ -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()
{
Expand Down Expand Up @@ -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;
}


Expand Down
69 changes: 69 additions & 0 deletions tests/Database/DatabaseEloquentModelTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'));
Expand Down Expand Up @@ -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'));
Expand Down