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 Morphmap Laravel Package

moneo/laravel-morphmap

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Installation:

    composer require moneo/laravel-morphmap
    

    No additional configuration or service provider registration is required.

  2. Apply the Trait: Add HasCustomMorphMap to your model:

    use Moneo\LaravelMorphMap\HasCustomMorphMap;
    
    class Post extends Model
    {
        use HasCustomMorphMap;
    
        // ...
    }
    
  3. Define Custom Morph Maps: Override the $customMorphMap property in your model’s constructor:

    protected $customMorphMap = [
        'author' => [
            'App\Models\User' => 'user',
            'App\Models\GuestAuthor' => 'guest',
        ],
        'commenter' => [
            'App\Models\User' => 'user',
            'App\Models\AICommenter' => 'ai',
        ],
    ];
    
  4. First Use Case: Define a polymorphic relationship (e.g., belongsTo) and use the custom morph map:

    public function author()
    {
        return $this->belongsTo(User::class, 'author_id')->setMorphClass('author');
    }
    

    Now, when querying Post::find(1)->author, the morph map for the author relation will be used.


Implementation Patterns

Usage Patterns

  1. Per-Relationship Morph Maps: Define distinct morph maps for each polymorphic relationship on a model. For example:

    $customMorphMap = [
        'owner' => ['App\Models\Admin' => 'admin', 'App\Models\User' => 'user'],
        'moderator' => ['App\Models\Moderator' => 'mod', 'App\Models\SuperModerator' => 'supermod'],
    ];
    
  2. Dynamic Morph Maps: Override the $customMorphMap property dynamically based on conditions (e.g., tenant ID):

    public function __construct(array $attributes = [])
    {
        parent::__construct($attributes);
        $this->customMorphMap = $this->resolveMorphMapByTenant();
    }
    
  3. Integration with Eloquent Relationships: Use the trait with standard polymorphic relationships:

    public function comments()
    {
        return $this->morphMany(Comment::class, 'commentable')->setMorphClass('commenter');
    }
    
  4. Fallback to Global Morph Map: If a relationship lacks a custom morph map, Laravel’s global morphMap will be used automatically.

Workflows

  1. Migrating Existing Polymorphic Relations:

    • Update relationships to use setMorphClass($relationName).
    • Define custom morph maps for each relation in $customMorphMap.
    • Test queries to ensure the correct morph types are resolved.
  2. API Versioning: Use different morph maps for different API versions by conditionally setting $customMorphMap in the constructor.

  3. Testing: Mock the $customMorphMap property in tests to isolate behavior:

    $post = new Post();
    $post->customMorphMap = ['author' => ['App\Models\User' => 'test_user']];
    

Integration Tips

  1. Leverage with API Resources: Customize JSON:API or REST responses by mapping morph types to consistent API formats:

    public function toArray($request)
    {
        return [
            'author_type' => $this->author->getMorphClass(),
            'author_data' => $this->author->toArray(),
        ];
    }
    
  2. Combine with Policies: Use morph maps to enforce access control:

    public function authorize(Post $post, User $user)
    {
        return $post->author->getMorphClass() === 'admin' || $user->isAdmin();
    }
    
  3. Seeding Data: Ensure seeded polymorphic relations use the correct morph types:

    Post::create([
        'title' => 'Test Post',
        'author_id' => 1,
        'author_type' => 'user', // Explicitly set if needed
    ]);
    

Gotchas and Tips

Pitfalls

  1. Case Sensitivity: Morph map keys (e.g., 'user') are case-sensitive. Ensure consistency:

    // Correct:
    $customMorphMap = ['author' => ['App\Models\User' => 'user']];
    
    // Incorrect (will fail):
    $customMorphMap = ['author' => ['App\Models\User' => 'User']];
    
  2. Missing setMorphClass: Forgetting to call setMorphClass($relationName) on a polymorphic relationship will cause the global morph map to be used instead of the custom one:

    // Wrong: Uses global morphMap
    return $this->belongsTo(User::class, 'author_id');
    
    // Correct: Uses custom morphMap for 'author'
    return $this->belongsTo(User::class, 'author_id')->setMorphClass('author');
    
  3. Circular Dependencies: Avoid circular references in morph maps (e.g., ModelA referencing ModelB which references ModelA with conflicting maps). Test thoroughly.

  4. Database Consistency: Ensure the *_type column in your database matches the keys defined in $customMorphMap. Mismatches will cause ModelNotFoundException.

Debugging

  1. Check Morph Resolution: Log the resolved morph class to debug:

    dd($this->author->getMorphClass()); // Debug the actual resolved type
    
  2. Verify Custom Morph Map: Temporarily add a dump() to confirm the map is loaded:

    public function __construct(array $attributes = [])
    {
        parent::__construct($attributes);
        dump($this->customMorphMap); // Debug in Tinker or tests
    }
    
  3. Query Builder Issues: If queries fail, check for typos in relationship names or morph map keys. Use Laravel’s query logging:

    DB::enableQueryLog();
    $post = Post::find(1);
    dd(DB::getQueryLog());
    

Tips

  1. Use Enums for Morph Types: Define morph types as enums for type safety and autocompletion:

    enum AuthorType: string
    {
        case USER = 'user';
        case GUEST = 'guest';
    }
    

    Then reference them in $customMorphMap:

    $customMorphMap = ['author' => [User::class => AuthorType::USER->value]];
    
  2. Leverage Model Events: Dynamically update morph maps during events (e.g., retrieved):

    protected static function boot()
    {
        static::retrieved(function ($model) {
            if ($model->isDemoMode()) {
                $model->customMorphMap['author'] = ['App\Models\User' => 'demo_user'];
            }
        });
    }
    
  3. Document Morph Maps: Add PHPDoc comments to clarify morph map usage:

    /**
     * Custom morph maps for polymorphic relations.
     *
     * @var array{
     *     author: array<class-string, string>, // Maps author relations
     *     commenter: array<class-string, string> // Maps commenter relations
     * }
     */
    protected $customMorphMap;
    
  4. Performance: Cache the resolved morph maps if your application has high query volume:

    protected function getCustomMorphMap(string $relation): array
    {
        return cache()->remember("morphmap_{$this->id}_{$relation}", now()->addHours(1), function () {
            return $this->customMorphMap[$relation] ?? [];
        });
    }
    

    (Note: Requires extending the trait or using a macro.)

  5. Testing Edge Cases: Test with:

    • Empty morph maps.
    • Non-existent relationship names.
    • Null or invalid morph types in the database.
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.
emuniq/filament-browser-notifications
syriable/filament-translator
hungnm28/livewire-form
wenprise/eloquent
crudly/encrypted
fadion/bouncy
cuci/prototurk-sdk
gos/pubsub-router-bundle
cuci/prototurk-sdk-symfony
clementtalleu/easyadmin-markdown-bundle
codeflextech/permission-manager
karnoweb/livewire-datepicker
sayedenam/sayed-dashboard
milito/query-filter
apiboxsym/user-bundle
apiboxsym/health-check-bundle
jayeshmepani/jpl-moshier-ephemeris-php
elnasnato/laraliveui
labrodev/rest-sdk
sampaui/sampaui