Weave 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

Tentacles Laravel Package

greabock/tentacles

Laravel package for hierarchical data: add a tree trait to Eloquent models to manage nested sets, with helpers for ancestors, descendants, siblings, moving nodes, and rebuilding trees. A lightweight fit for categories, menus, and threaded comments.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Verify compatibility before anything else. The last tagged release is from November 2017, which puts it in the Laravel 5.5/5.6 era. Check composer.json in the repo for illuminate/* constraints and PHP requirement, then try composer require greabock/tentacles on a scratch branch. On Laravel 9+ you will almost certainly need a fork with loosened constraints and PHP 8 signature fixes.
  2. Publish and inspect the migration. The package's whole value is one shared pivot table (columns pairing *_type / *_id for both sides of the relation). Publish it, read it, and add the composite indexes yourself before running it — see Gotchas.
  3. Register a morph map in a service provider before storing any rows. This package stores class names (or morph aliases) in the pivot; deciding this after you have data means a painful backfill:
    // AppServiceProvider::boot()
    Relation::morphMap([
        'post'    => \App\Models\Post::class,
        'video'   => \App\Models\Video::class,
        'comment' => \App\Models\Comment::class,
    ]);
    
  4. Add the trait to both ends of the relationship and define the relation method. The exact trait/method names are small and sparsely documented — open src/ and read the trait before you write anything; don't trust blog posts from 2017.
  5. First use case: you have Post, Video, and Page, and want to attach the same Tag (or Attachment, or SeoMeta) to all three without three separate pivot tables. That "attach anything to anything, one table" scenario is what makes this package worth a look over hand-rolled morphToMany pairs. Do that end-to-end in tinker first: attach, reload, query back, detach.

Implementation Patterns

Trait on both sides, relation on the owning side. Typical usage: a shared model (tag/label/attachment) gets queried from any parent, and parents expose a tentacles()-style relation returning the collection. Treat the shared model as the "tentacle" and the parents as "hosts".

class Post extends Model
{
    use \Greabock\Tentacles\...; // trait from src/

    public function tags()
    {
        return $this->tentacles(); // confirm exact signature in the trait
    }
}

Attach/detach/sync instead of raw pivot writes. The package's helper methods are the reason to use it — they handle the type/id pairing and dedupe. Wrap multi-model operations in a transaction, because a partial sync leaves a half-attached record that's annoying to detect later:

DB::transaction(function () use ($post, $tags) {
    $post->syncTentacles($tags); // verify name: attach/detach/sync variants
});

Eager loading is non-negotiable. Polymorphic pivots are the classic N+1 trap. Always ->with('tags') when listing hosts, and use whereHas for filtering rather than loading and filtering in PHP:

Post::with('tags')->whereHas('tags', fn ($q) => $q->where('slug', 'laravel'))->paginate(20);

Extra pivot data via withPivot. If you attach metadata (position, weight, created_by), declare it explicitly and pass it as the second argument to attach. Otherwise it silently disappears on reload.

Service-layer wrapper. Because the package is thin and unmaintained, isolate it: put attach/detach/sync behind your own Tagger/AttachmentService class. If you later swap to native morphToMany or spatie/laravel-tags, you change one file, not 40 controllers.

Admin multi-select binding. For forms, hydrate a list of IDs from the relation, submit the array, and diff in the service layer. Avoid sync directly off $request->all() — validate IDs with exists rules per morph type first.

Reverse lookups. If the shared model needs "what is attached to me" (e.g. a tag showing its posts), you need a relation back through the same pivot. Confirm the package supports the inverse; if not, define a plain morphToMany on that side pointing at the same table.

Gotchas and Tips

  • Maintenance risk is the headline. 36 stars and no release since 2017 means no Laravel 10/11 support, no PHP 8.2 guarantees, and no CVEs triaged. Before committing, ask whether native morphToMany (with a shared taggables-style table plus morph map) covers 90% of the need with zero dependency risk. Often it does.
  • Morph map is forever. If the pivot stores FQCNs and you later namespace-move a model, every old row orphans. Register aliases early and never rename them.
  • Composite indexes. The published migration frequently lacks them. Add (tentacle_type, tentacle_id) and the mirror pair — polymorphic pivots degrade badly without both directions indexed.
  • Uniqueness. Add a unique constraint across the four columns (or dedupe in code) or you'll accumulate duplicate attachments from double form submits.
  • No cascade deletes. Deleting a Post leaves orphan pivot rows. Add a deleting observer that detaches, or a scheduled cleanup that removes rows whose morph type/id no longer resolve.
  • morphTo eager-load with aliases. When filtering by type, use the morph alias, not the class name, in where('tentacle_type', ...). Mismatches here are the #1 silent-empty-result cause.
  • Debugging: log ->toSql() with bindings for the relation query, and in tinker inspect the pivot table directly — DB::table('tentacles')->where(...) — to confirm rows are being written with the values you expect. If a relation returns empty despite rows existing, it's nearly always a morph alias vs FQCN mismatch.
  • PHP 8 friction: old packages trip on Collection type hints, missing return types, and Illuminate\Support\Arr signature changes. Budget a few hours for a fork; keep the fork as a VCS repository in composer.json and pin the commit.
  • Extension points to prefer: your own pivot model (extends Pivot, adds casts and accessors), a repository/service wrapper, and Laravel events in the service layer (TagAttached, TagDetached) — these survive a package swap, whereas anything touching Greabock\Tentacles\* directly does not.
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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin