Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Markable Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Installation:

    composer require maize-tech/laravel-markable
    php artisan markable:install
    
    • Publishes the config file and sets up the default user model (App\Models\User).
  2. Publish Migrations: Choose the mark types you need (e.g., bookmark, favorite, like, reaction) and publish their migrations:

    php artisan vendor:publish --tag="markable-migration-like"
    php artisan migrate
    
  3. Apply to a Model: Add the Markable trait and define allowed marks in your model:

    use Maize\Markable\Markable;
    use Maize\Markable\Models\Like;
    
    class Course extends Model
    {
        use Markable;
    
        protected static $marks = [
            Like::class,
        ];
    }
    
  4. First Usage:

    $course = Course::first();
    $user = auth()->user();
    
    Like::add($course, $user); // Add a like
    Like::toggle($course, $user); // Toggle like
    Like::has($course, $user); // Check if liked
    

Implementation Patterns

Core Workflows

  1. Mark Management:

    • Add/Remove/Toggle: Use Mark::add(), Mark::remove(), or Mark::toggle() for CRUD operations.
      Like::add($model, $user, ['metadata' => 'value']);
      Like::remove($model, $user);
      
    • Check Existence: Use Mark::has($model, $user, $value) to verify if a mark exists.
    • Count Marks: Use Mark::count($model, $value) to get the total count of a specific mark.
  2. Eloquent Relationships:

    • Fetch Marks: Access marks via Eloquent relations:
      $course->likes; // Collection of Like models
      $course->likers; // Collection of users who liked the course
      
    • Filter Models: Use dynamic scopes to query marked models:
      Course::whereHasLike($user)->get(); // Courses liked by $user
      Post::whereHasReaction($user, 'heart')->get(); // Posts with 'heart' reactions
      
  3. Custom Marks:

    • Define a New Mark:
      class Bookmark extends Mark
      {
          public static function markableRelationName(): string
          {
              return 'bookmarkers';
          }
      }
      
    • Configure Allowed Values (e.g., for Reaction):
      'allowed_values' => [
          'reaction' => ['heart', 'kissing_heart', '*'], // Wildcard allows any value
      ],
      
  4. Metadata Handling:

    • Pass custom metadata when adding a mark:
      Like::add($course, $user, ['topic' => 'Laravel', 'reason' => 'useful']);
      
    • Retrieve metadata via the mark model:
      $like = Like::where('markable_id', $course->id)->first();
      $like->metadata; // ['topic' => 'Laravel', ...]
      
  5. BackedEnum Support (v3.0+):

    • Define an enum for type-safe values:
      enum ReactionType: string { case Like = 'like'; }
      
    • Configure in config/markable.php:
      'allowed_values' => [
          'reaction' => ReactionType::class,
      ],
      
    • Use enums directly in methods:
      Reaction::add($post, $user, ReactionType::Like);
      

Integration Tips

  1. API Endpoints:

    • Create routes for mark actions (e.g., POST /posts/{post}/like):
      Route::post('/posts/{post}/like', function (Post $post) {
          Like::toggle($post, auth()->user());
          return response()->json(['status' => 'success']);
      });
      
  2. Frontend Integration:

    • Use JavaScript to toggle marks dynamically:
      document.querySelector('.like-button').addEventListener('click', async () => {
          const response = await fetch(`/posts/${postId}/like`, { method: 'POST' });
          // Update UI based on response
      });
      
  3. Caching:

    • Cache mark counts or user lists to reduce database load:
      $count = Cache::remember("like_count_{$course->id}", now()->addHours(1), function () {
          return Like::count($course);
      });
      
  4. Validation:

    • Validate mark values against allowed_values in config:
      use Maize\Markable\Exceptions\InvalidMarkValueException;
      
      try {
          Reaction::add($post, $user, 'invalid_value');
      } catch (InvalidMarkValueException $e) {
          // Handle invalid value
      }
      
  5. Testing:

    • Test mark operations in PHPUnit:
      public function test_like_toggle()
      {
          $course = Course::factory()->create();
          $user = User::factory()->create();
      
          Like::toggle($course, $user);
          $this->assertTrue(Like::has($course, $user));
      }
      

Gotchas and Tips

Pitfalls

  1. Migration Conflicts:

    • If you modify mark tables manually, ensure the table_prefix in config/markable.php matches the published migrations.
    • Fix: Re-publish migrations or manually adjust the prefix.
  2. Wildcard Values:

    • Using '*' in allowed_values disables validation for that mark, allowing any value. Use sparingly.
    • Tip: Combine with enums for safer wildcard usage:
      'allowed_values' => [
          'reaction' => [ReactionType::class, '*'], // Allows enums + any string
      ],
      
  3. Performance with Large Datasets:

    • Queries like whereHasLike() can be slow on tables with millions of records.
    • Fix: Add database indexes to user_id and markable_id columns in mark tables.
  4. User Model Assumption:

    • The package assumes a User model with an id column. Customize user_model in config if using a different auth system.
  5. Metadata Serialization:

    • Metadata is stored as JSON. Ensure your data is JSON-serializable (no resources or closures).
    • Tip: Use json_encode()/json_decode() for complex objects.
  6. Dynamic Scopes:

    • Scopes like whereHasLike() are generated dynamically. Override markRelationName() if the default pluralization doesn’t fit your naming convention.

Debugging

  1. Mark Not Found:

    • Verify the mark class is listed in the model’s $marks array.
    • Check if the migration for the mark table exists and was run.
  2. Invalid Mark Value:

    • Ensure the value passed to add()/toggle() matches allowed_values in config.
    • Enable debug mode to see validation errors:
      try {
          Reaction::add($post, $user, 'invalid');
      } catch (Exception $e) {
          dd($e->getMessage());
      }
      
  3. Relationships Not Loading:

    • Ensure the mark model’s markableRelationName() and markRelationName() methods return correct relation names.
    • Debug: Dump the relations:
      dd($course->relations); // Check if 'likes' or 'likers' exist
      

Extension Points

  1. Custom Mark Logic:

    • Extend the Mark class to add business logic (e.g., auto-remove old marks):
      class Bookmark extends Mark
      {
          protected static function boot()
          {
              parent::boot();
              static::created(function ($mark) {
                  // Add custom logic here
              });
          }
      }
      
  2. Observers:

    • Use Eloquent observers to trigger actions on mark changes:
      Like::observe(LikeObserver::class);
      
      class LikeObserver
      {
          public function created(Like $like)
          {
              // Notify user or update analytics
          }
      }
      
  3. Policy Integration:

    • Restrict mark actions with Laravel Policies:
      class LikePolicy
      {
          public function toggle(User $user, Course $course)
          {
              return $user->can('like_courses');
          }
      }
      
      Register in AuthServiceProvider:
      Gate::define('like_courses', function (User $user) {
          return $user->isAdmin();
      });
      
  4. API Resources:

    • Customize mark responses with API Resources:
      class LikeResource extends JsonResource
      {
          public function toArray($request)
          {
              return [
                  'user' => $this->user,
                  '
      
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle
atriumphp/atrium
sandermuller/package-boost-laravel
sandermuller/boost-skills
redaxo/core
yusufgenc/filament-api-forge
l3aro/rating-star-for-filament
leek/filament-subtenant-scope
anil/file-picker
broqit/fields-ai