maize-tech/laravel-markable
Add likes, bookmarks, favorites, reactions and more to Laravel models with a simple “markable” system. Includes install command, configurable user model and table prefix, and optional publishable migrations per mark type for quick setup.
Installation:
composer require maize-tech/laravel-markable
php artisan markable:install
Publish only the migrations you need (e.g., bookmark, like, reaction):
php artisan vendor:publish --tag="markable-migration-like"
php artisan migrate
Configure User Model:
Update config/markable.php to specify your user model:
'user_model' => App\Models\User::class,
First Use Case:
Add the Markable trait to a model (e.g., Post) and define supported marks:
use Maize\Markable\Markable;
use Maize\Markable\Models\Like;
class Post extends Model
{
use Markable;
protected static $marks = [
Like::class,
];
}
Basic Usage:
$post = Post::first();
$user = auth()->user();
Like::add($post, $user); // Add like
Like::toggle($post, $user); // Toggle like
Like::has($post, $user); // Check if liked
Like::count($post); // Count likes
Model Integration:
Markable trait in Eloquent models to enable marks.$marks (e.g., Like, Bookmark, custom marks).Mark Management:
Like::add(), Like::remove(), Like::toggle()).Like::add($post, $user, ['topic' => 'Laravel'])).Querying Marks:
Like::count($post).Like::has($post, $user).Post::whereHasLike($user).Custom Marks:
Maize\Markable\Mark and override markableRelationName()/markRelationName().class Bookmark extends Mark {
public static function markableRelationName(): string { return 'bookmarkers'; }
}
Value-Based Marks (e.g., Reactions):
config/markable.php:
'allowed_values' => [
'reaction' => ['heart', 'thumbs-up'],
],
Reaction::add($post, $user, 'heart').BackedEnum Support:
ReactionType) and set it in config:
'allowed_values' => [
'reaction' => \App\Enums\ReactionType::class,
],
Reaction::add($post, $user, ReactionType::Heart);
API Endpoints:
Create routes for mark actions (e.g., POST /posts/{post}/like).
Example controller:
public function like(Post $post) {
Like::toggle($post, auth()->user());
return response()->json(['status' => 'success']);
}
Frontend Integration:
Use JavaScript to toggle marks (e.g., fetch /posts/{post}/like).
Example Blade:
<button onclick="toggleLike({{ $post->id }})">
{{ Like::has($post, auth()->user()) ? 'Unlike' : 'Like' }}
</button>
Eager Loading:
Optimize queries with with():
Post::with(['likes', 'reacters'])->get();
Caching:
Cache mark counts/metadata (e.g., Cache::remember()) for performance.
Migration Conflicts:
table_prefix in config (default: markable_).Null User Checks:
$user before calling mark methods (e.g., auth()->user() may return null).Value Validation:
allowed_values is configured, passing invalid values (e.g., Reaction::add($post, $user, 'invalid')) will throw an exception.*) allows any value but bypasses validation.Enum Type Safety:
BackedEnum, ensure the enum’s backing type matches the database column (e.g., string for value column).Relation Naming:
markableRelationName() must match the pivot table’s relation name in the model.Like → likes).Mass Assignment:
Query Logs: Enable Laravel’s query logging to debug pivot table queries:
DB::enableQueryLog();
Like::add($post, $user);
dd(DB::getQueryLog());
Common Errors:
ClassNotFound: Ensure mark classes (e.g., Like) are autoloaded.TableNotFound: Run migrations or check table_prefix.CallToUndefinedMethod: Verify relation names in custom marks.Custom Mark Logic:
Override methods in Maize\Markable\Mark (e.g., boot() for model events).
Event Listeners:
Listen for mark events (e.g., MarkAdded, MarkRemoved) via Laravel’s event system.
Policy Integration: Use Laravel’s policies to authorize mark actions:
public function toggleLike(User $user, Post $post) {
$this->authorize('like', $post);
Like::toggle($post, $user);
}
Testing:
Use Markable trait in tests:
public function test_like_toggle() {
$post = new Post();
$user = new User();
Like::toggle($post, $user);
$this->assertTrue(Like::has($post, $user));
}
Performance:
chunk() for bulk mark updates.user_id and markable_id in pivot tables.Table Prefix:
Changing table_prefix after initial setup requires updating all migrations and existing data.
Allowed Values:
Empty arrays ([]) disable values entirely. Use * for wildcards.
User Model:
Ensure the configured user_model implements Illuminate\Contracts\Auth\Authenticatable.
Dynamic Mark Registration: Register marks dynamically in a service provider:
public function boot() {
if (!app()->runningInConsole()) {
Mark::extend('DynamicMark', function () {
return new class extends Mark { /* ... */ };
});
}
}
Soft Deletes:
Extend marks to support soft deletes by adding SoftDeletes trait to the Mark model.
API Resources:
Create custom resources for marks (e.g., LikeResource) to shape responses:
public function toArray($request) {
return [
'user' => $this->user,
'metadata' => $this->metadata,
];
}
Validation: Validate mark values in Form Requests:
public function rules() {
return [
'reaction' => ['required', Rule::in(['heart', 'thumbs-up'])],
];
}
How can I help you explore Laravel packages today?