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

[11.x] Alternate implementation to allow "safe" objects to be used by Eloquent models (without breaking mass assignment protection) #50218

Draft
wants to merge 9 commits into
base: 11.x
Choose a base branch
from
17 changes: 9 additions & 8 deletions src/Illuminate/Database/Eloquent/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\ForwardsCalls;
use Illuminate\Support\ValidatedInput;
use ReflectionClass;
use ReflectionMethod;

Expand Down Expand Up @@ -151,10 +152,10 @@ public function __construct(QueryBuilder $query)
/**
* Create and return an un-saved model instance.
*
* @param array $attributes
* @param array|ValidatedInput $attributes
* @return \Illuminate\Database\Eloquent\Model|static
*/
public function make(array $attributes = [])
public function make(array|ValidatedInput $attributes = [])
{
return $this->newModelInstance($attributes);
}
Expand Down Expand Up @@ -1017,10 +1018,10 @@ protected function ensureOrderForCursorPagination($shouldReverse = false)
/**
* Save a new model and return the instance.
*
* @param array $attributes
* @param array|ValidatedInput $attributes
* @return \Illuminate\Database\Eloquent\Model|$this
*/
public function create(array $attributes = [])
public function create(array|ValidatedInput $attributes = [])
{
return tap($this->newModelInstance($attributes), function ($instance) {
$instance->save();
Expand All @@ -1030,10 +1031,10 @@ public function create(array $attributes = [])
/**
* Save a new model and return the instance. Allow mass-assignment.
*
* @param array $attributes
* @param array|ValidatedInput $attributes
* @return \Illuminate\Database\Eloquent\Model|$this
*/
public function forceCreate(array $attributes)
public function forceCreate(array|ValidatedInput $attributes)
{
return $this->model->unguarded(function () use ($attributes) {
return $this->newModelInstance()->create($attributes);
Expand All @@ -1043,10 +1044,10 @@ public function forceCreate(array $attributes)
/**
* Save a new model instance with mass assignment without raising model events.
*
* @param array $attributes
* @param array|ValidatedInput $attributes
* @return \Illuminate\Database\Eloquent\Model|$this
*/
public function forceCreateQuietly(array $attributes = [])
public function forceCreateQuietly(array|ValidatedInput $attributes = [])
{
return Model::withoutEvents(fn () => $this->forceCreate($attributes));
}
Expand Down
35 changes: 20 additions & 15 deletions src/Illuminate/Database/Eloquent/Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use Illuminate\Support\Collection as BaseCollection;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\ForwardsCalls;
use Illuminate\Support\ValidatedInput;
use JsonSerializable;
use LogicException;
use Stringable;
Expand Down Expand Up @@ -228,10 +229,10 @@ abstract class Model implements Arrayable, ArrayAccess, CanBeEscapedWhenCastToSt
/**
* Create a new Eloquent model instance.
*
* @param array $attributes
* @param array|ValidatedInput $attributes
* @return void
*/
public function __construct(array $attributes = [])
public function __construct(array|ValidatedInput $attributes = [])
{
$this->bootIfNotBooted();

Expand Down Expand Up @@ -501,15 +502,19 @@ public static function withoutBroadcasting(callable $callback)
/**
* Fill the model with an array of attributes.
*
* @param array $attributes
* @param array|ValidatedInput $attributes
* @return $this
*
* @throws \Illuminate\Database\Eloquent\MassAssignmentException
*/
public function fill(array $attributes)
public function fill(array|ValidatedInput $attributes)
{
$totallyGuarded = $this->totallyGuarded();

if ($attributes instanceof ValidatedInput) {
$attributes = $attributes->all();
}

$fillable = $this->fillableFromArray($attributes);

foreach ($fillable as $key => $value) {
Expand Down Expand Up @@ -551,10 +556,10 @@ public function fill(array $attributes)
/**
* Fill the model with an array of attributes. Force mass assignment.
*
* @param array $attributes
* @param array|ValidatedInput $attributes
* @return $this
*/
public function forceFill(array $attributes)
public function forceFill(array|ValidatedInput $attributes)
{
return static::unguarded(fn () => $this->fill($attributes));
}
Expand Down Expand Up @@ -590,11 +595,11 @@ public function qualifyColumns($columns)
/**
* Create a new instance of the given model.
*
* @param array $attributes
* @param array|ValidatedInput $attributes
* @param bool $exists
* @return static
*/
public function newInstance($attributes = [], $exists = false)
public function newInstance(array|ValidatedInput $attributes = [], $exists = false)
{
// This method just provides a convenient way for us to generate fresh model
// instances of this current model. It is particularly useful during the
Expand All @@ -611,7 +616,7 @@ public function newInstance($attributes = [], $exists = false)

$model->mergeCasts($this->casts);

$model->fill((array) $attributes);
$model->fill($attributes);

return $model;
}
Expand Down Expand Up @@ -982,11 +987,11 @@ protected function incrementOrDecrement($column, $amount, $extra, $method)
/**
* Update the model in the database.
*
* @param array $attributes
* @param array|ValidatedInput $attributes
* @param array $options
* @return bool
*/
public function update(array $attributes = [], array $options = [])
public function update(array|ValidatedInput $attributes = [], array $options = [])
{
if (! $this->exists) {
return false;
Expand All @@ -998,13 +1003,13 @@ public function update(array $attributes = [], array $options = [])
/**
* Update the model in the database within a transaction.
*
* @param array $attributes
* @param array|ValidatedInput $attributes
* @param array $options
* @return bool
*
* @throws \Throwable
*/
public function updateOrFail(array $attributes = [], array $options = [])
public function updateOrFail(array|ValidatedInput $attributes = [], array $options = [])
{
if (! $this->exists) {
return false;
Expand All @@ -1016,11 +1021,11 @@ public function updateOrFail(array $attributes = [], array $options = [])
/**
* Update the model in the database without raising any events.
*
* @param array $attributes
* @param array|ValidatedInput $attributes
* @param array $options
* @return bool
*/
public function updateQuietly(array $attributes = [], array $options = [])
public function updateQuietly(array|ValidatedInput $attributes = [], array $options = [])
{
if (! $this->exists) {
return false;
Expand Down
5 changes: 3 additions & 2 deletions src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Illuminate\Database\Query\Grammars\MySqlGrammar;
use Illuminate\Database\UniqueConstraintViolationException;
use Illuminate\Support\Str;
use Illuminate\Support\ValidatedInput;
use InvalidArgumentException;

class BelongsToMany extends Relation
Expand Down Expand Up @@ -1273,12 +1274,12 @@ public function saveManyQuietly($models, array $pivotAttributes = [])
/**
* Create a new instance of the related model.
*
* @param array $attributes
* @param array|ValidatedInput $attributes
* @param array $joining
* @param bool $touch
* @return \Illuminate\Database\Eloquent\Model
*/
public function create(array $attributes = [], array $joining = [], $touch = true)
public function create(array|ValidatedInput $attributes = [], array $joining = [], $touch = true)
{
$instance = $this->related->newInstance($attributes);

Expand Down
21 changes: 11 additions & 10 deletions src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\Concerns\InteractsWithDictionary;
use Illuminate\Database\UniqueConstraintViolationException;
use Illuminate\Support\ValidatedInput;

abstract class HasOneOrMany extends Relation
{
Expand Down Expand Up @@ -46,10 +47,10 @@ public function __construct(Builder $query, Model $parent, $foreignKey, $localKe
/**
* Create and return an un-saved instance of the related model.
*
* @param array $attributes
* @param array|ValidatedInput $attributes
* @return \Illuminate\Database\Eloquent\Model
*/
public function make(array $attributes = [])
public function make(array|ValidatedInput $attributes = [])
{
return tap($this->related->newInstance($attributes), function ($instance) {
$this->setForeignAttributesForCreate($instance);
Expand Down Expand Up @@ -331,10 +332,10 @@ public function saveManyQuietly($models)
/**
* Create a new instance of the related model.
*
* @param array $attributes
* @param array|ValidatedInput $attributes
* @return \Illuminate\Database\Eloquent\Model
*/
public function create(array $attributes = [])
public function create(array|ValidatedInput $attributes = [])
{
return tap($this->related->newInstance($attributes), function ($instance) {
$this->setForeignAttributesForCreate($instance);
Expand All @@ -346,21 +347,21 @@ public function create(array $attributes = [])
/**
* Create a new instance of the related model without raising any events to the parent model.
*
* @param array $attributes
* @param array|ValidatedInput $attributes
* @return \Illuminate\Database\Eloquent\Model
*/
public function createQuietly(array $attributes = [])
public function createQuietly(array|ValidatedInput $attributes = [])
{
return Model::withoutEvents(fn () => $this->create($attributes));
}

/**
* Create a new instance of the related model. Allow mass-assignment.
*
* @param array $attributes
* @param array|ValidatedInput $attributes
* @return \Illuminate\Database\Eloquent\Model
*/
public function forceCreate(array $attributes = [])
public function forceCreate(array|ValidatedInput $attributes = [])
{
$attributes[$this->getForeignKeyName()] = $this->getParentKey();

Expand All @@ -370,10 +371,10 @@ public function forceCreate(array $attributes = [])
/**
* Create a new instance of the related model with mass assignment without raising model events.
*
* @param array $attributes
* @param array|ValidatedInput $attributes
* @return \Illuminate\Database\Eloquent\Model
*/
public function forceCreateQuietly(array $attributes = [])
public function forceCreateQuietly(array|ValidatedInput $attributes = [])
{
return Model::withoutEvents(fn () => $this->forceCreate($attributes));
}
Expand Down
9 changes: 5 additions & 4 deletions src/Illuminate/Database/Eloquent/Relations/MorphMany.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\ValidatedInput;

class MorphMany extends MorphOneOrMany
{
Expand Down Expand Up @@ -67,10 +68,10 @@ public function match(array $models, Collection $results, $relation)
/**
* Create a new instance of the related model. Allow mass-assignment.
*
* @param array $attributes
* @param array|ValidatedInput $attributes
* @return \Illuminate\Database\Eloquent\Model
*/
public function forceCreate(array $attributes = [])
public function forceCreate(array|ValidatedInput $attributes = [])
{
$attributes[$this->getMorphType()] = $this->morphClass;

Expand All @@ -80,10 +81,10 @@ public function forceCreate(array $attributes = [])
/**
* Create a new instance of the related model with mass assignment without raising model events.
*
* @param array $attributes
* @param array|ValidatedInput $attributes
* @return \Illuminate\Database\Eloquent\Model
*/
public function forceCreateQuietly(array $attributes = [])
public function forceCreateQuietly(array|ValidatedInput $attributes = [])
{
return Model::withoutEvents(fn () => $this->forceCreate($attributes));
}
Expand Down
5 changes: 3 additions & 2 deletions src/Illuminate/Database/Eloquent/Relations/MorphOneOrMany.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\ValidatedInput;

abstract class MorphOneOrMany extends HasOneOrMany
{
Expand Down Expand Up @@ -70,10 +71,10 @@ public function addEagerConstraints(array $models)
/**
* Create a new instance of the related model. Allow mass-assignment.
*
* @param array $attributes
* @param array|ValidatedInput $attributes
* @return \Illuminate\Database\Eloquent\Model
*/
public function forceCreate(array $attributes = [])
public function forceCreate(array|ValidatedInput $attributes = [])
{
$attributes[$this->getForeignKeyName()] = $this->getParentKey();
$attributes[$this->getMorphType()] = $this->morphClass;
Expand Down
21 changes: 21 additions & 0 deletions tests/Database/DatabaseEloquentModelTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
use Illuminate\Support\InteractsWithTime;
use Illuminate\Support\Str;
use Illuminate\Support\Stringable;
use Illuminate\Support\ValidatedInput;
use InvalidArgumentException;
use LogicException;
use Mockery as m;
Expand Down Expand Up @@ -625,6 +626,18 @@ public function testMakeMethodDoesNotSaveNewModel()
$this->assertSame('taylor', $model->name);
}

public function testMakeMethodAllowsSafeObject()
{
$model = EloquentModelSaveStub::make(new ValidatedInput(['name' => 'taylor']));
$this->assertSame('taylor', $model->name);
}

public function testConstructorAllowsSafeObject()
{
$model = new EloquentModelSaveStub(new ValidatedInput(['name' => 'taylor']));
$this->assertSame('taylor', $model->name);
}

public function testForceCreateMethodSavesNewModelWithGuardedAttributes()
{
$_SERVER['__eloquent.saved'] = false;
Expand Down Expand Up @@ -1434,6 +1447,14 @@ public function testFillable()
$this->assertSame('bar', $model->age);
}

public function testFillingWithSafeObject()
{
$model = new EloquentModelStub;
$model->fill(new ValidatedInput(['name' => 'foo', 'age' => 'bar']));
$this->assertSame('foo', $model->name);
$this->assertSame('bar', $model->age);
}

public function testQualifyColumn()
{
$model = new EloquentModelStub;
Expand Down
Loading
Loading