更新一個entity,就跟儲存一個新entity一樣,需要使用save方法:
$user = User::find(1); $user->email = '[email protected]'; $user->save();
背後發生了什麼事呢?讓我們一一來看。
/**
* Save the model to the database.
*
* @param array $options
* @return bool
*/
public function save(array $options = array())
{
$query = $this->newQueryWithoutScopes();
// If the "saving" event returns false we'll bail out of the save and return
// false, indicating that the save failed. This gives an opportunities to
// listeners to cancel save operations if validations fail or whatever.
if ($this->fireModelEvent('saving') === false)
{
return false;
}
// If the model already exists in the database we can just update our record
// that is already in this database using the current IDs in this "where"
// clause to only update this model. Otherwise, we'll just insert them.
if ($this->exists)
{
$saved = $this->performUpdate($query, $options);
}
// If the model is brand new, we'll insert it into our database and set the
// ID attribute on the model to the value of the newly inserted row's ID
// which is typically an auto-increment value managed by the database.
else
{
$saved = $this->performInsert($query, $options);
}
if ($saved) $this->finishSave($options);
return $saved;
}
在前一篇Eloquent CRUD:find我們看到了Eloquent\Model大量運用到Eloquent\Builder和Query\Builder類別。
newQueryWithoutScopes從命名可猜想是在製作Query\Builder實體。
我們把重點放在performUpdate和performInsert吧
/**
* Perform a model update operation.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return bool|null
*/
protected function performUpdate(Builder $query, array $options)
{
$dirty = $this->getDirty();
if (count($dirty) > 0)
{
// If the updating event returns false, we will cancel the update operation so
// developers can hook Validation systems into their models and cancel this
// operation if the model does not pass validation. Otherwise, we update.
if ($this->fireModelEvent('updating') === false)
{
return false;
}
// First we need to create a fresh query instance and touch the creation and
// update timestamp on the model which are maintained by us for developer
// convenience. Then we will just continue saving the model instances.
if ($this->timestamps && array_get($options, 'timestamps', true))
{
$this->updateTimestamps();
}
// Once we have run the update operation, we will fire the "updated" event for
// this model instance. This will allow developers to hook into these after
// models are updated, giving them a chance to do any special processing.
$dirty = $this->getDirty();
if (count($dirty) > 0)
{
$this->setKeysForSaveQuery($query)->update($dirty);
$this->fireModelEvent('updated', false);
}
}
return true;
}
首先利用getDirty得知哪些屬性被更動過、需要更新:
/**
* Get the attributes that have been changed since last sync.
*
* @return array
*/
public function getDirty()
{
$dirty = array();
foreach ($this->attributes as $key => $value)
{
if ( ! array_key_exists($key, $this->original))
{
$dirty[$key] = $value;
}
elseif ($value !== $this->original[$key] &&
! $this->originalIsNumericallyEquivalent($key))
{
$dirty[$key] = $value;
}
}
return $dirty;
}
看起來是在比較attributes跟original陣列的內容,但是我們設定value時並沒有碰到它們呀?
$user->email = '[email protected]';
我們並不是
$user->attributes['email'] = '[email protected]';
getDirty怎麼會比較那兩個陣列呢?
原來是magic method __set
/**
* Dynamically set attributes on the model.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function __set($key, $value)
{
$this->setAttribute($key, $value);
}
除此之外,performUpdate的其他部份看起來都很直觀。
接著看performInsert:
/**
* Perform a model insert operation.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @return bool
*/
protected function performInsert(Builder $query, array $options)
{
if ($this->fireModelEvent('creating') === false) return false;
// First we'll need to create a fresh query instance and touch the creation and
// update timestamps on this model, which are maintained by us for developer
// convenience. After, we will just continue saving these model instances.
if ($this->timestamps && array_get($options, 'timestamps', true))
{
$this->updateTimestamps();
}
// 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;
if ($this->incrementing)
{
$this->insertAndSetId($query, $attributes);
}
// If the table is not incrementing we'll simply insert this attributes as they
// are, as this attributes arrays must contain an "id" column already placed
// there by the developer as the manually determined key for these models.
else
{
$query->insert($attributes);
}
// We will go ahead and set the exists property to true, so that it is set when
// the created event is fired, just in case the developer tries to update it
// during the event. This will allow them to do so and run an update here.
$this->exists = true;
$this->fireModelEvent('created', false);
return true;
}