Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
22 changes: 14 additions & 8 deletions src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,7 @@ public function getAttributeValue($key)
*/
protected function getAttributeFromArray($key)
Copy link
Author

@ug-christoph ug-christoph Nov 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of calling

return $this->getAttributes()[$key] ?? null;

I do exactly what getAttributes does, but instead of calling mergeAttributesFromCachedCasts
I call the new
mergeAttributeFromCachedCasts($key) so it only does it for the attribute I am currently trying to get

{
return $this->getAttributes()[$key] ?? null;
return $this->getAttributes($key)[$key] ?? null;
}

/**
Expand Down Expand Up @@ -1890,20 +1890,23 @@ protected function parseCasterClass($class)
*
* @return void
*/
protected function mergeAttributesFromCachedCasts()
protected function mergeAttributesFromCachedCasts(?string $onlyMergeCachedCastsForThisKey = null)
{
$this->mergeAttributesFromClassCasts();
$this->mergeAttributesFromAttributeCasts();
$this->mergeAttributesFromClassCasts($onlyMergeCachedCastsForThisKey);
$this->mergeAttributesFromAttributeCasts($onlyMergeCachedCastsForThisKey);
}

/**
* Merge the cast class attributes back into the model.
*
* @return void
*/
protected function mergeAttributesFromClassCasts()
protected function mergeAttributesFromClassCasts(?string $onlyMergeCachedCastsForThisKey = null)
{
foreach ($this->classCastCache as $key => $value) {
if (! is_null($onlyMergeCachedCastsForThisKey) && $key !== $onlyMergeCachedCastsForThisKey) {
continue;
}
$caster = $this->resolveCasterClass($key);

$this->attributes = array_merge(
Expand All @@ -1920,9 +1923,12 @@ protected function mergeAttributesFromClassCasts()
*
* @return void
*/
protected function mergeAttributesFromAttributeCasts()
protected function mergeAttributesFromAttributeCasts(?string $onlyMergeCachedCastsForThisKey = null)
{
foreach ($this->attributeCastCache as $key => $value) {
if (! is_null($onlyMergeCachedCastsForThisKey) && $key !== $onlyMergeCachedCastsForThisKey) {
continue;
}
$attribute = $this->{Str::camel($key)}();

if ($attribute->get && ! $attribute->set) {
Expand Down Expand Up @@ -1959,9 +1965,9 @@ protected function normalizeCastClassResponse($key, $value)
*
* @return array<string, mixed>
*/
public function getAttributes()
public function getAttributes(?string $onlyMergeCachedCastsForThisKey = null)
{
$this->mergeAttributesFromCachedCasts();
$this->mergeAttributesFromCachedCasts($onlyMergeCachedCastsForThisKey);

return $this->attributes;
}
Expand Down
Copy link
Author

@ug-christoph ug-christoph Nov 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The affected tests are quite brittle (they do an assertions on the exact call count to a method). The PRs goal is to directly reduce those calls. The test is a good example of where the optimization helps. In these tests $subject->id is called which causes mergeAttributesFromCachedCasts to run for the encrypted/casted columns (secret_array, secret_collection). This is now expectedly not happening anymore and I have therefore just reduced the call count in the assertion by one to reflect this improvement

Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ public function testAsEncryptedCollection()
->with('{"key1":"value1"}')
->andReturn('encrypted-secret-collection-string-1');
$this->encrypter->expects('encryptString')
->times(10)
->times(9)
->with('{"key1":"value1","key2":"value2"}')
->andReturn('encrypted-secret-collection-string-2');
$this->encrypter->expects('decryptString')
Expand Down Expand Up @@ -239,7 +239,7 @@ public function testAsEncryptedCollectionMap()
->with('[{"key1":"value1"}]')
->andReturn('encrypted-secret-collection-string-1');
$this->encrypter->expects('encryptString')
->times(12)
->times(11)
->with('[{"key1":"value1"},{"key2":"value2"}]')
->andReturn('encrypted-secret-collection-string-2');
$this->encrypter->expects('decryptString')
Expand Down Expand Up @@ -295,7 +295,7 @@ public function testAsEncryptedArrayObject()
->with('encrypted-secret-array-string-1')
->andReturn('{"key1":"value1"}');
$this->encrypter->expects('encryptString')
->times(10)
->times(9)
->with('{"key1":"value1","key2":"value2"}')
->andReturn('encrypted-secret-array-string-2');
$this->encrypter->expects('decryptString')
Expand Down