adrolli/eloquent-mutators
Define reusable Eloquent accessors and mutators outside your models. Apply the same transformation logic across multiple models or multiple attributes on one model using a base model class or a trait, with config and extensible registration via a service provider.
Installation:
composer require awobaz/eloquent-mutators
php artisan mutators:install
config/mutators.php) and registers the service provider.Model Integration: Choose one of these approaches:
use Awobaz\Mutator\Database\Eloquent\Model;
class Post extends Model { ... }
Mutable trait:
use Awobaz\Mutator\Mutable;
class Post extends \Illuminate\Database\Eloquent\Model {
use Mutable;
}
First Use Case: Define accessors/mutators in your model:
protected $accessors = [
'title' => 'trim_whitespace',
];
Now, $post->title will automatically trim whitespace when accessed.
Reusable Transformations:
slug generation) in a shared mutator:
protected $accessors = [
'slug' => 'slug',
'meta_title' => 'title_case',
];
Dynamic Mutators:
Mutator::extend('custom_logic', function ($model, $value, $key) {
return $value * $model->price_multiplier;
});
protected $accessors = [
'discounted_price' => ['custom_logic' => ['price_multiplier' => 0.9]],
];
Conditional Mutators:
getAttribute() for conditional logic:
public function getTitleAttribute($value) {
return $this->title_trimmed ?? trim($value);
}
protected $accessors = ['title_trimmed' => 'trim_whitespace'];
Bulk Model Processing:
$posts = Post::all()->map(function ($post) {
return $post->fresh(); // Re-fetch to trigger accessors
});
API Responses:
protected $accessors = [
'formatted_name' => ['capitalize_words', 'slug'],
];
return $post->only(['formatted_name']);
Mutator facade to test isolated mutator logic:
$this->mock(Mutator::class)->shouldReceive('applyAccessors');
Mutator::extend('cached_slug', function ($model, $value) {
return $model->cache->remember("slug_{$value}", now()->addHours(1), function () use ($value) {
return Str::slug($value);
});
});
protected $mutators = [
'email' => 'lower_case',
];
Circular Dependencies:
title → slug → title).getAttribute() for complex dependencies.Performance Overhead:
getAttribute() for lazy evaluation.Configuration Conflicts:
$accessors) may clash with Laravel’s reserved properties.config/mutators.php:
'accessors_property' => 'custom_accessors',
Parameter Parsing:
'str_replace:one,two') fails if parameters contain commas.'content' => ['str_replace' => ['one', 'two']]
Trait vs. Base Class:
Mutable trait may conflict with other traits (e.g., SoftDeletes).Awobaz\Mutator\Database\Eloquent\Model for large projects.Mutator facade to log inputs/outputs:
Mutator::extend('debug', function ($model, $value, $key) {
\Log::debug("Mutator $key: $value");
return $value;
});
$accessors/$mutators to bypass them during debugging.Custom Mutators:
Mutator::extend('dynamic_mutator', function ($model, $value, $key, $param) {
return $value . "_$param";
});
$model->setMutator('dynamic_field', 'dynamic_mutator:suffix');
Model Events:
retrieved):
protected static function booted() {
static::retrieved(function ($model) {
$model->refreshMutators();
});
}
API Resources:
toArray() for consistent API responses:
public function toArray($request) {
return [
'title' => $this->title, // Triggers accessors
'slug' => $this->slug,
];
}
app_slug) to avoid collisions./**
* @mutator Converts text to kebab-case.
*/
Mutator::extend('kebab_case', ...);
fresh() to test mutators in isolation:
$post = Post::find(1)->fresh();
$this->assertEquals('trimmed-title', $post->title);
How can I help you explore Laravel packages today?