Installation:
composer require sofa/eloquence-mutable
Register the service provider in config/app.php:
'providers' => [
// ...
Sofa\Eloquence\Mutable\MutableServiceProvider::class,
],
First Use Case:
Apply the Mutable trait to a model (e.g., User.php):
use Sofa\Eloquence\Mutable\Mutable;
class User extends Model
{
use Mutable;
}
Define Mutators:
Override getMutators() to define attribute transformations:
protected function getMutators(): array
{
return [
'full_name' => [
'get' => fn ($value) => ucwords($value),
'set' => fn ($value) => strtolower($value),
],
'age' => [
'get' => fn ($value) => $value ? 'Adult' : 'Minor',
'set' => fn ($value) => $value === 'Adult' ? 18 : null,
],
];
}
Usage:
$user = new User();
$user->full_name = 'john doe'; // Automatically transformed on set
echo $user->full_name; // "John Doe" (transformed on get)
Attribute Transformation:
Use getMutators() to define dynamic get/set logic for attributes. Example:
'email' => [
'get' => fn ($value) => strtolower($value),
'set' => fn ($value) => filter_var($value, FILTER_SANITIZE_EMAIL),
],
Conditional Mutators: Leverage closures for runtime logic:
'status' => [
'get' => fn ($value) => $value === 'active' ? '✅' : '❌',
'set' => fn ($value) => $value === '✅' ? 'active' : 'inactive',
],
Related Model Mutators:
Transform nested attributes (e.g., user.address.city):
'address.city' => [
'get' => fn ($value) => ucfirst($value),
'set' => fn ($value) => strtolower($value),
],
Validation Integration:
Combine with Validable trait for pre-set validation:
use Sofa\Eloquence\Validable\Validable;
class User extends Model
{
use Mutable, Validable;
protected function getMutators(): array { /* ... */ }
protected function getRules(): array { return ['email' => 'required|email']; }
}
API Response Formatting: Use mutators to format responses (e.g., hide sensitive data):
'password' => [
'get' => fn () => '********',
],
Searchable for search-as-you-type with transformed attributes.toArray()).registerMutatorEvents():
protected function registerMutatorEvents(): array
{
return [
'full_name' => [
'set' => fn ($model, $old, $new) => event(new NameUpdated($model, $old, $new)),
],
];
}
Circular References:
Avoid recursive mutators (e.g., user.address.city → address.user).
Fix: Use ignoreMutators() to exclude nested paths:
protected function ignoreMutators(): array
{
return ['address.user'];
}
Performance: Mutators run on every get/set, including mass assignment. For heavy logic, cache results:
'computed_field' => [
'get' => fn ($value) => $value ?? Cache::remember("user_{$this->id}_computed", now()->addHours(1), fn () => computeExpensiveValue()),
],
Database Sync:
Mutators do not auto-sync to the database. Use save() explicitly:
$user->full_name = 'Jane Doe'; // Only mutates in memory
$user->save(); // Persists transformed value
Serialization:
Mutators run during toArray()/toJson(). For large datasets, disable temporarily:
$user->disableMutators();
$data = $user->toArray();
$user->enableMutators();
Log Mutations: Enable debug mode to log all mutations:
config(['eloquence.mutable.debug' => true]);
Check Laravel logs for Sofa\Eloquence\Mutable entries.
Override Defaults: Reset mutators dynamically:
$user->resetMutators(['full_name']);
Custom Mutator Types:
Extend Sofa\Eloquence\Mutable\Mutator to add validation or side effects:
class LoggedMutator extends Mutator
{
public function set($model, $value)
{
Log::info("Mutating {$model->getTable()}.{$this->attribute}: $value");
return parent::set($model, $value);
}
}
Register via getMutatorClasses():
protected function getMutatorClasses(): array
{
return [LoggedMutator::class];
}
Dynamic Mutators: Load mutators from a config file or database:
protected function getMutators(): array
{
return Cache::remember('user_mutators', now()->addHours(1), fn () => config('mutators.user'));
}
Testing: Mock mutators in tests:
$user = new User();
$user->shouldReceive('mutateAttribute')->with('full_name', 'john')->andReturn('John');
How can I help you explore Laravel packages today?