Skip to content

Instantly share code, notes, and snippets.

@linaspasv
Last active June 2, 2020 15:01
Show Gist options
  • Save linaspasv/ad74e7077d212d3f6b79b8e51118267b to your computer and use it in GitHub Desktop.
Save linaspasv/ad74e7077d212d3f6b79b8e51118267b to your computer and use it in GitHub Desktop.
Attemt to solve an excessive custom casts ::set() calls for Eloquent Models
<?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