Installation:
composer require weebly/laravel-mutate
Ensure Weebly\Mutate\LaravelMutatorServiceProvider is registered in config/app.php (or use auto-discovery).
Publish Config:
php artisan vendor:publish --provider="Weebly\Mutate\LaravelMutatorServiceProvider"
This generates config/mutators.php with default settings.
Extend Your Model:
Replace your App\Models\Model with Weebly\Mutate\Database\Model in your base model or individual models.
Define Mutations:
Add a $mutate property to your model:
protected $mutate = [
'ip_address' => [
'get' => function ($value) {
return $value ? inet_ntop($value) : null;
},
'set' => function ($value) {
return $value ? inet_pton($value) : null;
},
],
'encrypted_data' => [
'get' => fn($value) => decrypt($value),
'set' => fn($value) => encrypt($value),
],
];
First Use Case:
$user = new User();
$user->ip_address = '192.168.1.1'; // Automatically converted to BINARY(16) on save
$user->save();
$storedIp = $user->fresh()->ip_address; // Returns readable string
Attribute Mapping:
Use $mutate to define bidirectional transformations for attributes:
$mutate = [
'attribute' => [
'get' => fn($value) => /* transform for retrieval */,
'set' => fn($value) => /* transform for storage */,
],
];
Database-Specific Optimizations:
BINARY or JSON in DB, decode in PHP:
$mutate = [
'metadata' => [
'get' => fn($value) => json_decode($value, true),
'set' => fn($value) => json_encode($value),
],
];
Encryption/Decryption:
$mutate = [
'ssn' => [
'get' => fn($value) => decrypt($value),
'set' => fn($value) => encrypt($value),
],
];
Conditional Mutations:
$mutate = [
'status' => [
'get' => fn($value) => $value === 'active' ? 'Active' : 'Inactive',
'set' => fn($value) => strtolower($value),
],
];
Mass Assignment Safety:
Use $guarded or $fillable alongside $mutate to control which attributes can be mutated.
Query Scopes: Mutations apply to retrieved data, so use them in scopes:
public function scopeActive($query) {
return $query->where('status', 'active'); // 'active' is stored as lowercase
}
API Resources: Mutations ensure consistent data shapes in responses:
// UserResource.php
public function toArray($request) {
return [
'ip' => $this->resource->ip_address, // Automatically decoded
];
}
Testing: Mock mutations in tests:
$model = new User();
$model->shouldReceive('mutateAttribute')
->with('ip_address', $value)
->andReturnTransformedValue();
Performance:
Relationships: Mutations work recursively for loaded relationships:
$user = User::with('posts')->find(1);
// $user->posts[0]->content is automatically mutated if defined.
Double Transformation:
get and set.$model->attribute = 'raw'; // set → stored
$model->fresh()->attribute; // get → retrieved
Database Schema Mismatch:
BINARY(16) for IPs).Circular Dependencies:
Mass Assignment Risks:
$fillable or $guarded.Lazy Loading:
Log Mutations:
Temporarily add logging to $mutate callbacks:
'get' => function ($value) {
\Log::debug("Mutating to get: $value");
return /* ... */;
},
Check Stored Values:
Use DB::table('users')->get() to verify raw DB values differ from model attributes.
Disable Mutations:
Override shouldMutateAttribute() in your model to debug:
protected function shouldMutateAttribute($attribute) {
return false; // Disable all mutations
}
Custom Mutator Classes: Extract complex mutations to standalone classes:
class IpMutator {
public static function encode($ip) { /* ... */ }
public static function decode($binary) { /* ... */ }
}
$mutate = [
'ip_address' => [
'get' => [IpMutator::class, 'decode'],
'set' => [IpMutator::class, 'encode'],
],
];
Global Mutations: Define mutations in a trait or base model to reuse across models:
trait Encryptable {
protected $mutate = [
'secret' => [
'get' => fn($value) => decrypt($value),
'set' => fn($value) => encrypt($value),
],
];
}
Dynamic Mutations: Use closures to conditionally apply mutations:
$mutate = [
'data' => [
'get' => function ($value) {
return request()->has('decrypt') ? decrypt($value) : $value;
},
'set' => fn($value) => encrypt($value),
],
];
Event-Based Mutations:
Trigger mutations via model events (e.g., retrieved):
protected static function booted() {
static::retrieved(function ($model) {
$model->mutateAttribute('dynamic_field', $model->dynamic_field);
});
}
Config Overrides:
Override default behavior in config/mutators.php:
'default_mutator' => \App\CustomMutator::class,
'skip_attributes' => ['timestamps'], // Exclude from mutation
How can I help you explore Laravel packages today?