composer require sansanlabs/laravel-userstamps
php artisan vendor:publish --tag="userstamps-config"
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('content');
$table->userstamps(); // Adds created_by_id, updated_by_id
$table->softUserstamps(); // Adds deleted_by_id (for soft deletes)
$table->timestamps();
});
use SanSanLabs\UserStamps\Concerns\HasUserstamps;
class Post extends Model
{
use HasUserstamps;
}
Automatically track who created/updated/deleted a record:
// In a controller or service
$post = new Post();
$post->title = 'Hello World';
$post->save();
// Access the creator
$user = $post->createdBy; // Returns the User model
Migration Setup:
userstamps() for basic tracking (created_by, updated_by).softUserstamps() if soft deletes are enabled (adds deleted_by).is_using_morph: true in config.Model-Level Access:
// Accessors provided by the trait
$post->createdBy; // User who created the record
$post->updatedBy; // User who last updated
$post->deletedBy; // User who soft-deleted (if applicable)
Automatic Population:
creating, updating, deleting).Auth::user().$model->createdBy = $user;.Enable in config:
'is_using_morph' => true,
_type columns (e.g., created_by_id, created_by_type).Team, Organization) as creators.Override defaults in config:
'users_model' => App\Models\Admin::class,
'users_table' => 'admins',
For mass updates/creates, manually set the user:
Post::create([
'title' => 'Bulk Post',
'content' => '...',
], ['created_by_id' => $admin->id]); // Override default auth user
Filter records by creator:
Post::whereCreatedBy($user)->get(); // Uses the trait's accessor
// Under the hood: where('created_by_id', $user->id)
with_trashed: true in config to include deleted records in queries.$post = Post::withTrashed()->find($id);
$post->deletedBy; // Returns the User who deleted it
actingAs() in Laravel’s testing helpers to simulate users:
$this->actingAs($user)->post('/posts')->assertCreated();
return PostResource::make($post)->additional(['created_by' => $post->createdBy]);
created, updated, or deleted events to log actions:
Post::created(function ($post) {
Log::info("Post created by {$post->createdBy->name}");
});
Migration Order:
Column not found when running migrations.userstamps() is called after the primary key ($table->id()) but before other columns.$table->id();
$table->userstamps(); // Correct order
$table->string('title');
Polymorphic Conflicts:
SQLSTATE[42S22]: Column not found when using morph with existing tables.is_using_morph: false) or manually add _type columns before enabling morph.Soft Deletes + Userstamps:
deleted_by columns are ignored if soft deletes are disabled in the model.use SoftDeletes; and use HasUserstamps; are on the model.Auth User Unavailable:
$post->createdBy ??= User::find(1); // Fallback
Check Config:
Run php artisan config:clear if userstamps aren’t populating. Verify config values:
php artisan config:get userstamps.*
Event Hooks: Add debug logs to event listeners:
Post::created(function ($post) {
\Log::debug('Created by:', ['user_id' => $post->created_by_id]);
});
Database Schema: Verify columns exist:
SHOW COLUMNS FROM posts LIKE '%by%';
Expected output for non-morph:
created_by_id | bigint
updated_by_id | bigint
deleted_by_id | bigint (if softUserstamps)
Custom Accessors: Override the trait’s accessors in your model:
public function getCreatedByAttribute($value) {
return $value ? User::find($value)->name : 'System';
}
Dynamic User Resolution: Extend the package to resolve users dynamically (e.g., from API tokens):
// In a service provider
UserStamps::resolveUsing(function () {
return Auth::guard('api')->user() ?? Auth::user();
});
Additional Stamps:
Add custom stamps (e.g., published_by) by extending the trait:
// In HasUserstamps.php (copy the trait and extend)
public static function bootHasUserstamps() {
static::created(function ($model) {
$model->published_by_id = auth()->id();
});
}
Testing Helpers: Create a custom macro for testing:
// In a TestCase
public function assertCreatedBy($model, $user) {
$this->assertEquals($user->id, $model->created_by_id);
}
Eager Loading:
Avoid N+1 queries when accessing createdBy:
$posts = Post::with('createdBy')->get();
Indexing: Add database indexes for frequent queries:
$table->foreignId('created_by_id')->constrained()->index();
Batch Updates:
For bulk updates, use update() instead of looping:
// Bad: N queries
foreach ($posts as $post) {
$post->update(['updated_by_id' => $user->id]);
}
// Good: 1 query
Post::whereIn('id', $postIds)->update(['updated_by_id' => $user->id]);
How can I help you explore Laravel packages today?