Installation:
composer require astalpaert/laravel-blamable
Run migrations (if using the default schema):
php artisan migrate
Model Integration:
Use the Blamable trait in your Eloquent model:
use Astalpaert\Blamable\Blamable;
class Post extends Model
{
use Blamable;
}
First Use Case: Automatically track who created/updated a record:
$post = new Post(['title' => 'Hello World']);
$post->save(); // Automatically sets `created_by` and `updated_by` to the authenticated user
config/blamable.php (customization options)database/migrations/ (default schema for created_by/updated_by columns)src/Blamable.php (trait logic)Automatic Tracking:
// No manual intervention needed; works with Laravel's default auth
$user = auth()->user();
$post = new Post(['title' => 'Test']);
$post->save(); // `created_by` = `$user->id`, `updated_by` = `$user->id`
Manual Overrides:
// Force a specific user (e.g., for API calls or admin actions)
$post = new Post(['title' => 'Admin Post']);
$post->setCreatedBy($adminUser);
$post->setUpdatedBy($adminUser);
$post->save();
Query Scopes:
// Filter by creator
$userPosts = Post::byUser($user)->get();
// Filter by updater
$recentlyUpdated = Post::updatedBy($user)->latest()->take(5)->get();
// In middleware
$request->user = $serviceAccountUser;
Post::withoutBlamable(function () {
Post::factory()->create();
});
SoftDeletes trait.Missing Columns:
created_by/updated_by columns don’t exist, the trait silently fails. Always run migrations first or define the schema manually:
Schema::table('posts', function (Blueprint $table) {
$table->foreignId('created_by')->constrained()->nullOnDelete();
$table->foreignId('updated_by')->constrained()->nullOnDelete();
});
Authentication Context:
created_by/updated_by will be null. Handle this explicitly:
$post->setCreatedBy(auth()->check() ? auth()->user() : null);
Mass Assignment:
created_by/updated_by in mass assignment arrays. Use the trait methods instead.Log Missing Users:
Add a listener to log when created_by/updated_by are null:
Post::created(function ($post) {
if (!$post->created_by) {
Log::warning('Post created without a user', ['post_id' => $post->id]);
}
});
Check Trait Loading: Verify the trait is loaded in your model:
dd(class_uses(Post::class)); // Should include `Blamable`
Custom User Resolution:
Override getBlamableUser() to fetch users from a non-standard source (e.g., API tokens):
class Post extends Model
{
use Blamable;
protected function getBlamableUser()
{
return $this->request?->user() ?? auth()->user();
}
}
Additional Metadata:
Extend the trait to track extra fields (e.g., created_ip):
use Astalpaert\Blamable\Blamable as BaseBlamable;
trait ExtendedBlamable {
use BaseBlamable;
protected static function booted()
{
static::creating(function ($model) {
$model->created_ip = request()->ip();
});
}
}
Custom Query Scopes: Add reusable scopes to your model:
class Post extends Model
{
use Blamable;
public function scopeByTeam($query, Team $team)
{
return $query->whereHas('user', function ($q) use ($team) {
$q->where('team_id', $team->id);
});
}
}
How can I help you explore Laravel packages today?