Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 52 additions & 19 deletions src/database/src/Eloquent/Concerns/HasAttributes.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,14 @@ trait HasAttributes
*/
protected ?array $mergedCastsCache = null;

/**
* The cached results of cast metadata predicate methods for this instance.
*
* Keyed by predicate name then attribute name. Cleared whenever cast state
* mutates via flushCastCaches().
*/
protected array $castMetadataCache = [];

/**
* The built-in, primitive cast types supported by Eloquent.
*
Expand Down Expand Up @@ -199,7 +207,7 @@ protected function initializeHasAttributes(): void
array_merge($this->casts, $this->casts()),
);

$this->mergedCastsCache = null;
$this->flushCastCaches();
}

/**
Expand Down Expand Up @@ -718,11 +726,20 @@ public function mergeCasts(array $casts): static

$this->casts = array_merge($this->casts, $casts);

$this->mergedCastsCache = null;
$this->flushCastCaches();

return $this;
}

/**
* Flush the per-instance cast metadata caches.
*/
protected function flushCastCaches(): void
{
$this->mergedCastsCache = null;
$this->castMetadataCache = [];
}

/**
* Ensure that the given casts are strings.
*/
Expand Down Expand Up @@ -873,10 +890,14 @@ protected function getEnumCastableAttributeValue(string $key, mixed $value): mix
*/
protected function getCastType(string $key): string
{
if (isset($this->castMetadataCache['castType'][$key])) {
return $this->castMetadataCache['castType'][$key];
}

$castType = $this->getCasts()[$key];

if (isset(static::$castTypeCache[$castType])) {
return static::$castTypeCache[$castType];
return $this->castMetadataCache['castType'][$key] = static::$castTypeCache[$castType];
}

if ($this->isCustomDateTimeCast($castType)) {
Expand All @@ -891,7 +912,7 @@ protected function getCastType(string $key): string
$convertedCastType = trim(strtolower($castType));
}

return static::$castTypeCache[$castType] = $convertedCastType;
return $this->castMetadataCache['castType'][$key] = static::$castTypeCache[$castType] = $convertedCastType;
}

/**
Expand Down Expand Up @@ -1533,31 +1554,31 @@ protected function casts(): array
*/
protected function isDateCastable(string $key): bool
{
return $this->hasCast($key, ['date', 'datetime', 'immutable_date', 'immutable_datetime']);
return $this->castMetadataCache['dateCastable'][$key] ??= $this->hasCast($key, ['date', 'datetime', 'immutable_date', 'immutable_datetime']);
}

/**
* Determine whether a value is Date / DateTime custom-castable for inbound manipulation.
*/
protected function isDateCastableWithCustomFormat(string $key): bool
{
return $this->hasCast($key, ['custom_datetime', 'immutable_custom_datetime']);
return $this->castMetadataCache['dateCastableWithCustomFormat'][$key] ??= $this->hasCast($key, ['custom_datetime', 'immutable_custom_datetime']);
}

/**
* Determine whether a value is JSON castable for inbound manipulation.
*/
protected function isJsonCastable(string $key): bool
{
return $this->hasCast($key, ['array', 'json', 'json:unicode', 'object', 'collection', 'encrypted:array', 'encrypted:collection', 'encrypted:json', 'encrypted:object']);
return $this->castMetadataCache['jsonCastable'][$key] ??= $this->hasCast($key, ['array', 'json', 'json:unicode', 'object', 'collection', 'encrypted:array', 'encrypted:collection', 'encrypted:json', 'encrypted:object']);
}

/**
* Determine whether a value is an encrypted castable for inbound manipulation.
*/
protected function isEncryptedCastable(string $key): bool
{
return $this->hasCast($key, ['encrypted', 'encrypted:array', 'encrypted:collection', 'encrypted:json', 'encrypted:object']);
return $this->castMetadataCache['encryptedCastable'][$key] ??= $this->hasCast($key, ['encrypted', 'encrypted:array', 'encrypted:collection', 'encrypted:json', 'encrypted:object']);
}

/**
Expand All @@ -1567,20 +1588,24 @@ protected function isEncryptedCastable(string $key): bool
*/
protected function isClassCastable(string $key): bool
{
if (isset($this->castMetadataCache['classCastable'][$key])) {
return $this->castMetadataCache['classCastable'][$key];
}

$casts = $this->getCasts();

if (! array_key_exists($key, $casts)) {
return false;
return $this->castMetadataCache['classCastable'][$key] = false;
}

$castType = $this->parseCasterClass($casts[$key]);

if (in_array($castType, static::$primitiveCastTypes)) {
return false;
return $this->castMetadataCache['classCastable'][$key] = false;
}

if (class_exists($castType)) {
return true;
return $this->castMetadataCache['classCastable'][$key] = true;
}

throw new InvalidCastException($this, $key, $castType);
Expand All @@ -1591,23 +1616,27 @@ protected function isClassCastable(string $key): bool
*/
protected function isEnumCastable(string $key): bool
{
if (isset($this->castMetadataCache['enumCastable'][$key])) {
return $this->castMetadataCache['enumCastable'][$key];
}

$casts = $this->getCasts();

if (! array_key_exists($key, $casts)) {
return false;
return $this->castMetadataCache['enumCastable'][$key] = false;
}

$castType = $casts[$key];

if (in_array($castType, static::$primitiveCastTypes)) {
return false;
return $this->castMetadataCache['enumCastable'][$key] = false;
}

if (is_subclass_of($castType, Castable::class)) {
return false;
return $this->castMetadataCache['enumCastable'][$key] = false;
}

return enum_exists($castType);
return $this->castMetadataCache['enumCastable'][$key] = enum_exists($castType);
}

/**
Expand All @@ -1617,13 +1646,17 @@ protected function isEnumCastable(string $key): bool
*/
protected function isClassDeviable(string $key): bool
{
if (isset($this->castMetadataCache['classDeviable'][$key])) {
return $this->castMetadataCache['classDeviable'][$key];
}

if (! $this->isClassCastable($key)) {
return false;
return $this->castMetadataCache['classDeviable'][$key] = false;
}

$castType = $this->resolveCasterClass($key);

return method_exists($castType::class, 'increment') && method_exists($castType::class, 'decrement');
return $this->castMetadataCache['classDeviable'][$key] = method_exists($castType::class, 'increment') && method_exists($castType::class, 'decrement');
}

/**
Expand All @@ -1633,7 +1666,7 @@ protected function isClassDeviable(string $key): bool
*/
protected function isClassSerializable(string $key): bool
{
return ! $this->isEnumCastable($key)
return $this->castMetadataCache['classSerializable'][$key] ??= ! $this->isEnumCastable($key)
&& $this->isClassCastable($key)
&& method_exists($this->resolveCasterClass($key), 'serialize');
}
Expand All @@ -1643,7 +1676,7 @@ protected function isClassSerializable(string $key): bool
*/
protected function isClassComparable(string $key): bool
{
return ! $this->isEnumCastable($key)
return $this->castMetadataCache['classComparable'][$key] ??= ! $this->isEnumCastable($key)
&& $this->isClassCastable($key)
&& method_exists($this->resolveCasterClass($key), 'compare');
}
Expand Down
8 changes: 4 additions & 4 deletions src/database/src/Eloquent/Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -1865,7 +1865,7 @@ public function setKeyName(string $key): static
{
$this->primaryKey = $key;

$this->mergedCastsCache = null;
$this->flushCastCaches();

return $this;
}
Expand Down Expand Up @@ -1893,7 +1893,7 @@ public function setKeyType(string $type): static
{
$this->keyType = $type;

$this->mergedCastsCache = null;
$this->flushCastCaches();

return $this;
}
Expand All @@ -1913,7 +1913,7 @@ public function setIncrementing(bool $value): static
{
$this->incrementing = $value;

$this->mergedCastsCache = null;
$this->flushCastCaches();

return $this;
}
Expand Down Expand Up @@ -2314,7 +2314,7 @@ public function __sleep(): array

$this->classCastCache = [];
$this->attributeCastCache = [];
$this->mergedCastsCache = null;
$this->flushCastCaches();
$this->relationAutoloadCallback = null;
$this->relationAutoloadContext = null;

Expand Down
2 changes: 1 addition & 1 deletion src/database/src/Eloquent/SoftDeletes.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public function initializeSoftDeletes(): void
$this->casts[$this->getDeletedAtColumn()] = 'datetime';
}

$this->mergedCastsCache = null;
$this->flushCastCaches();
}

/**
Expand Down
Loading