When trying to retrive dirty Fields from an Eloquent Model, which uses the new introduced Custom Cast-Mutator the called method originalIsEquivalent ignores the Custom Cast. This results in an always dirty attribute, even if the orginal and the new Attribute should be equal.
I am not sure why the originalIsEquivalent methodes checks only for primitive Cast Types, maybe this behavior is on purpose?
public function originalIsEquivalent($key)
{
if (! array_key_exists($key, $this->original)) {
return false;
}
$attribute = Arr::get($this->attributes, $key);
$original = Arr::get($this->original, $key);
if ($attribute === $original) {
return true;
} elseif (is_null($attribute)) {
return false;
} elseif ($this->isDateAttribute($key)) {
return $this->fromDateTime($attribute) ===
$this->fromDateTime($original);
} elseif ($this->hasCast($key, ['object', 'collection'])) {
return $this->castAttribute($key, $attribute) ==
$this->castAttribute($key, $original);
} elseif ($this->hasCast($key, ['real', 'float', 'double'])) {
if (($attribute === null && $original !== null) || ($attribute !== null && $original === null)) {
return false;
}
return abs($this->castAttribute($key, $attribute) - $this->castAttribute($key, $original)) < PHP_FLOAT_EPSILON * 4;
} elseif ($this->hasCast($key, static::$primitiveCastTypes)) { //--------------Edited----------------
return $this->castAttribute($key, $attribute) ===
$this->castAttribute($key, $original);
}
return is_numeric($attribute) && is_numeric($original)
&& strcmp((string) $attribute, (string) $original) === 0;
}
My quick fix was to override this method in my model which used the costum class and removed the types from the hasCast method call: $this->hasCast($key). Which resulted in the expected behavior.
Maybe someone can help if this is a bug?
Bonjour je suis debutant un conseil pour mes projets Laravel qui qui me signalent des erreurs introuvables
@Babou246 S'il vous pla卯t essayer un forum comme Laracasts et Laravel.io, ou Discord. Et de pr茅f茅rence en anglais, merci.
Share your model and custom cast class please.
Sure:
I added a birthday column to the user model:
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->dateTime('birthday');
$table->rememberToken();
$table->timestamps();
});
(I know DateTime doesn麓t make sence in this context. But i am bound to an already existing database in my real project, which uses dateTime for dates)
This is the model:
<?php
namespace App;
use App\Casts\DateString;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
use Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name', 'email', 'password', 'birthday'
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password', 'remember_token',
];
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
'birthday' => DateString::class
];
}
And this is the cast:
?php
namespace App\Casts;
use Carbon\Carbon;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
class DateString implements CastsAttributes
{
/**
* Cast the given value.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param mixed $value
* @param array $attributes
* @return string
* @throws \Exception
*/
public function get($model, $key, $value, $attributes)
{
return $value ? (new Carbon($value))->format("Y-m-d") : null;
}
/**
* Prepare the given value for storage.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param string $key
* @param mixed $value
* @param array $attributes
* @return mixed
* @throws \Exception
*/
public function set($model, $key, $value, $attributes)
{
if (is_string($value))
return $value;
else
return (new Carbon($value))->format("Y-m-d");
}
}
````
This is the result.
$user = factory(User::class)->create()
$user->refresh()
$user->birthday
=> "2020-01-01"
$user->getDirty()
=> []
$user->birthday = "2020-01-01"
=> "2020-01-01"
$user->getDirty()
=> [
"birthday" => "2020-01-01",
]
By the way. Is there a better way to handle Dates which have no concept of timezones? Problem is, if I use the default date Mutator, the result which comes out of my resource api is:
{
data: {
...
birthday: "2019-12-31T23:00:00.000000Z"
...
}
```
Which is a little bit annyoing, when trying to add this value to an date Input
Can you also share the code that reproduces the problem?
By the way. Is there a better way to handle Dates which have no concept of timezones?
You are responsible for this yourself. Save your datetimes in UTC and save the timezone in a separate column if you need it.
This is currently the intended behavior. It is difficult to know if a custom cast object is equivalent.
Should support be added for an optional method on the custom cast to check itself for equivalency?
Most helpful comment
Should support be added for an optional method on the custom cast to check itself for equivalency?