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

Tag Module Laravel Package

idavoll/tag-module

A Laravel/PHP tag module from the Idavoll ecosystem that adds tagging support for your application's content. Attach, organize, and retrieve tags through a reusable module designed to keep tag management consistent across your project.

View on GitHub
Deep Wiki
Context7

Getting Started

Read the metadata before you read the code: this repo is [READ ONLY], points at AsgardCMS/Platform, and has 0 stars / 0 dependents. The description calls it "a lightweight tagging package for Laravel," but the sibling assessments confirm that's aspirational — the actual source is a ZF2-era AsgardCMS module. So the first real step is a decision, not an install:

  1. Clone and skim the tree first. Open Module.php, module.config.php, and src/. That tells you within minutes whether you're dealing with ZF2 conventions (ServiceManager, Zend\Db gateways, Zend\Form) or Laravel conventions. It will be ZF2.
  2. Pick your mode. Three, and only three:
    • Legacy sustainment — you have a live AsgardCMS 1.x install and need to understand or extend the existing tag schema. In that case this module isn't standalone; it's part of the platform. You don't composer require it into a new app.
    • Reference reading — you're designing tagging for a modern Laravel app and want a second opinion on the polymorphic pivot shape, slug rules, and cascade behavior. Read-only is fine here.
    • Modern build — you actually need tagging in a new Laravel app. Then close the tab and use spatie/laravel-tags, or hand-roll it. This repo is not on the table as a dependency.
  3. First use case (reference mode). Read three files in order, then stop: (a) the tags migration, (b) the taggable pivot migration, (c) the trait or service that exposes attach / sync / detach plus the query scopes. You'll have the whole design model in your head in ~20 minutes, and it's a good one.
  4. First use case (legacy mode). The module only boots under the Asgard module loader. In a live Asgard install, the day-to-day flow is: seed a vocabulary through the admin CRUD, then attach tags to content entities through the module's trait, then filter the front end using the query scopes. Confirm the migrations have already run and that the tags and taggable tables exist before touching anything.

If you're in reference mode, the fastest useful action is to write down the schema you want — a tags table (id, name, slug, timestamps), a polymorphic pivot (tag_id, taggable_type, taggable_id, unique across all three), a HasTags trait with morphToMany, and two scopes (withAnyTag, withAllTags) — and use the repo only to sanity-check details like slug uniqueness and cascade semantics. That's the whole value proposition in one sitting.

Implementation Patterns

Reference pattern: mirror the polymorphic model, not the code. The parts worth copying are conceptual:

  • Pivot shape. taggable_type + taggable_id + tag_id, with a compound unique index across all three to make attach idempotent at the DB level, not just in application code. The package leans on this; so should you.
  • Slug as the join key for reads. Tag lookups by slug (URL-friendly, case-normalized, URL-safe) rather than by display name. It survives user-facing input ("Laravel", "laravel", "Laravel!") collapsing into the same canonical tag, and it makes /tags/laravel the natural filter URL.
  • Sync-over-attach semantics. The pattern is: accept an array of tag names/slugs, resolve-or-create each one, then sync() the pivot. Prefer this over an attach loop — an attach loop accumulates duplicates and never removes anything, which is exactly the tag-hygiene mess you're trying to avoid.
  • Two scopes, not one. withAnyTag([...]) for OR-style filtering and withAllTags([...]) for the AND case (e.g. "show me posts tagged php and laravel"). They answer different product questions; ship both from day one.

Legacy pattern (Asgard extension). If you're adding a feature to a live install, follow the module's own wiring instead of inventing a new one: new admin route in the module's route config, new controller under the module's namespace, new form built with the module's Zend\Form conventions, and any table you touch mapped through its Zend\Db gateway. Resist the temptation to bolt on an Eloquent model alongside the gateway — you'll end up with two different data-access idioms on the same table, which is a debugging tax you can't pay down.

Integration tip for the modern build. In your own HasTags trait:

public function tags(): MorphToMany { return $this->morphToMany(Tag::class, 'taggable'); }

public function syncTags(array $names): void
{
    $ids = collect($names)
        ->map(fn ($n) => Str::slug($n))
        ->filter()
        ->unique()
        ->map(fn ($slug) => Tag::firstOrCreate(['slug' => $slug], ['name' => ...])->id);

    $this->tags()->sync($ids);
}

public function scopeWithAnyTag($q, array $slugs) { /* whereHas */ }
public function scopeWithAllTags($q, array $slugs) { /* whereHas with count == count */ }

That's roughly the whole feature, and it's a day's work — which is the honest argument against integrating a ZF2 module for the same outcome.

Where to look for design inspiration in the repo. The tags table + pivot migration gives you the data model. Any "tag cloud" or aggregation query in the module gives you the read-side shape (usually a GROUP BY tag_id with a count, which you can express in Eloquent with withCount). The admin CRUD tells you what fields and validation the original team considered necessary — name, slug, and often nothing else.

Gotchas and Tips

  • License is unresolved (NOASSERTION). This is a hard stop for shipping any code from the repo into a commercial product until counsel reads the actual grant. Reference reading (concepts, schema shapes) is fine; copy-pasting source is not, until cleared.
  • "Wrap it in a service provider" is not a real integration path. The module depends on Zend\Db, Zend\Form, ServiceManager, and the Asgard module loader. There is no seam to wrap — you'd be reimplementing the ZF2 runtime first. If a task description says "integrate this module into our Laravel app," push back on scope before you start.
  • Assume modern PHP breaks it. PHP 5.x-era code and PHP 8.2/8.3 don't get along: dynamic properties, removed functions, and looser type coercion will surface at runtime, not at boot. Any legacy work needs an explicit compatibility spike on the target runtime.
  • No tests, no CI, no security contact. Zero stars and zero dependents means zero community pressure. Anything you find is yours to fix forever, and anything user-facing (tag input, tag rendering) is an unpatched XSS surface.
  • Orphaned pivot rows are the classic defect. Deleting a Tag or a tagged model must cascade the pivot, or you accumulate dangling taggable_ids that silently break counts and filters. Enforce with ON DELETE CASCADE at the DB level, and also use morphMany/morphToMany detach hooks in app code — belt and braces, because both have failure modes.
  • Slug uniqueness under concurrency. firstOrCreate in a loop is a race waiting to happen under load. Normalize input to a slug, dedupe the array in PHP, then rely on the unique index plus a small retry or a transaction around the resolve-or-create step.
  • Case-sensitivity divergence between databases. MySQL's default collation treats Laravel and laravel as the same tag; Postgres does not. Normalize to slug before the insert, not after — otherwise the same user behavior produces two tags in one database and one tag in another.
  • Composite index order matters on the polymorphic pivot. For "tags for this post" queries you want (taggable_type, taggable_id) first; for "all posts with this tag" you want (tag_id, taggable_type). Ship both indexes; you'll need both directions and they don't share.
  • The real product risk isn't the code — it's tag hygiene. Synonyms, merges, orphan cleanup, and who owns the vocabulary determine whether tagging helps discovery or becomes a junk drawer. Budget for it regardless of what you build on.
  • Extension points (legacy only). If you must extend the module in an Asgard install, the touchpoints are module.config.php for table/model names and routing, and the module's view helpers for rendering tag lists and clouds. Changing the table name without updating both the config and the migrations will produce confusing "table not found" errors that look like a boot failure but aren't.
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.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core