-
-
Save linaspasv/ad74e7077d212d3f6b79b8e51118267b to your computer and use it in GitHub Desktop.
Attemt to solve an excessive custom casts ::set() calls for Eloquent Models
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Foundation\Database\Eloquent\Traits; | |
use Illuminate\Contracts\Database\Eloquent\CastsInboundAttributes; | |
use Illuminate\Database\Eloquent\Builder; | |
trait CustomCastsOptimizer | |
{ | |
/** | |
* Determine if the model or any of the given attribute(s) have been modified. | |
* | |
* @param array|string|null $attributes | |
* @return bool | |
*/ | |
public function isDirty($attributes = null) | |
{ | |
$attributes = is_array($attributes) ? $attributes : func_get_args(); | |
foreach ($attributes ?: array_keys($this->attributes) as $key) { | |
if (! array_key_exists($key, $this->attributes)) { | |
continue; | |
} | |
$this->mergeAttributeFromClassCasts($key); | |
if (! $this->originalIsEquivalent($key)) { | |
return true; | |
} | |
} | |
return false; | |
} | |
/** | |
* Get an attribute from the model. | |
* | |
* @param string $key | |
* @return mixed | |
*/ | |
public function getAttribute($key) | |
{ | |
if (! $key) { | |
return; | |
} | |
if (isset($this->classCastCache[$key])) { | |
return $this->classCastCache[$key]; | |
} | |
return parent::getAttribute($key); | |
} | |
/** | |
* Get an attribute from the $attributes array. | |
* | |
* @param string $key | |
* @return mixed | |
*/ | |
protected function getAttributeFromArray($key) | |
{ | |
$this->mergeAttributeFromClassCasts($key); | |
return $this->attributes[$key] ?? null; | |
} | |
/** | |
* Sync multiple original attribute with their current values. | |
* | |
* @param array|string $attributes | |
* @return $this | |
*/ | |
public function syncOriginalAttributes($attributes) | |
{ | |
$attributes = is_array($attributes) ? $attributes : func_get_args(); | |
foreach ($attributes as $attribute) { | |
$this->original[$attribute] = $this->getAttributeFromArray($attribute); | |
} | |
return $this; | |
} | |
/** | |
* Merge a single cast class attribute back into the model. | |
* | |
* @param string $key | |
* @return void | |
*/ | |
protected function mergeAttributeFromClassCasts($key) | |
{ | |
if (! isset($this->classCastCache[$key])) { | |
return; | |
} | |
$value = $this->classCastCache[$key]; | |
$caster = $this->resolveCasterClass($key); | |
$this->attributes = array_merge( | |
$this->attributes, | |
$caster instanceof CastsInboundAttributes | |
? [$key => $value] | |
: $this->normalizeCastClassResponse($key, $caster->set($this, $key, $value, $this->attributes)) | |
); | |
} | |
/** | |
* Merge the cast class attributes back into the model. | |
* | |
* @return void | |
*/ | |
protected function mergeAttributesFromClassCasts() | |
{ | |
foreach ($this->classCastCache as $key => $value) { | |
$this->mergeAttributeFromClassCasts($key); | |
} | |
} | |
/** | |
* Perform a model update operation. | |
* | |
* @param \Illuminate\Database\Eloquent\Builder $query | |
* @return bool | |
*/ | |
protected function performUpdate(Builder $query) | |
{ | |
// 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. | |
$changes = $this->getDirty(); | |
if ($changes) { | |
// 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->usesTimestamps()) { | |
$this->updateTimestamps(); | |
} | |
$this->setKeysForSaveQuery($query)->update($changes); | |
$this->changes = $changes; | |
$this->fireModelEvent('updated', false); | |
} | |
return true; | |
} | |
/** | |
* Save the model to the database. | |
* | |
* @param array $options | |
* @return bool | |
*/ | |
public function save(array $options = []) | |
{ | |
$query = $this->newModelQuery(); | |
// If the "saving" event returns false we'll bail out of the save and return | |
// false, indicating that the save failed. This provides a chance for any | |
// 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); | |
} | |
// 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); | |
if (! $this->getConnectionName() && | |
$connection = $query->getConnection()) { | |
$this->setConnection($connection->getName()); | |
} | |
} | |
// If the model is successfully saved, we need to do a few more things once | |
// that is done. We will call the "saved" method here to run any actions | |
// we need to happen after a model gets successfully saved right here. | |
if ($saved) { | |
$this->finishSave($options); | |
} | |
return $saved; | |
} | |
/** | |
* Perform any actions that are necessary after the model is saved. | |
* | |
* @param array $options | |
* @return void | |
*/ | |
protected function finishSave(array $options) | |
{ | |
$this->fireModelEvent('saved', false); | |
if (($this->wasRecentlyCreated || $this->changes) && ($options['touch'] ?? true)) { | |
$this->touchOwners(); | |
} | |
$this->original = $this->attributes; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment