roundly-consulting/tags-for-laravel
Add tags to any Eloquent model in Laravel. Attach, detach, and query tags through a simple, expressive API, with polymorphic relationships so posts, users, or any other model can be tagged and filtered with ease.
Architecture fit. The package is a Laravel-native tagging component: polymorphic taggable relations, per-type tag namespaces, and a first-party popular-tags endpoint (GET tags/{type}/popular?on=). It leans on Eloquent global scopes for visibility semantics, which is the idiomatic Laravel multi-tenancy and soft-delete pattern. For a Laravel monolith or modular monolith already using Eloquent scopes for tenant isolation, this is a low-friction fit. For services that filter tenancy outside Eloquent (query builders, raw SQL, repository layers), the fit is weaker because the package's correctness guarantees are scope-dependent.
Integration feasibility. Drop-in via Composer, publishable migrations, and route registration. The on query parameter implies the caller passes the taggable model (or morph alias), so integration requires a configured morph map and consistent type naming. Effort is low-to-moderate; the main integration work is confirming that our tenant and soft-delete global scopes are actually registered on every model passed via on, and that the tags route sits behind the same auth/tenant middleware as the rest of the API.
Technical risk. Upgrade risk from 1.0.1 to 1.0.2 is low — patch release, no breaking changes, no deprecations, no API surface changes. However, the fix itself is risk-relevant: prior to 1.0.2, popular-tag counts ignored global scopes, meaning soft-deleted records and other tenants' records inflated counts. That is a cross-tenant metadata leak (counts reveal activity volume) and a correctness bug in usage analytics. Anyone shipping 1.0.0–1.0.1 should treat this as a security-adjacent correctness fix and enforce >=1.0.2. Residual risk is concentrated in caching: if tag counts are cached above the query layer, the fix will not propagate until caches are invalidated.
Key questions.
on a fully-qualified class name or morph alias, and does it validate against an allowlist? Unvalidated class resolution is a potential attack surface.Stack fit. Assumes Laravel 10/11 and PHP 8.1+. The package composes cleanly with standard Laravel auth, middleware groups, and Eloquent scoping. It does not appear to impose a tenancy implementation; it inherits whatever global scopes the host app defines. That is good for portability and bad for guarantee — correctness is contingent on our own scope discipline.
Migration path. Pin ^1.0.2 as the floor. Existing installs on 1.0.0/1.0.1 need a Composer update plus cache clears (php artisan cache:clear, and any tagged/CDN cache for the endpoint). No schema migration is implied by the release note; verify against the package changelog before assuming. If we maintain a fork or a vendored copy, cherry-pick the scoping change rather than the full release.
Compatibility. Backward compatible at the API contract level: same route, same parameters, same response shape. Behavior changes only for records excluded by global scopes — counts will drop for tenants with soft-deleted data, which downstream dashboards may notice as a step change. Confirm that no consumer depends on the previously inflated totals before rollout, and annotate the change in internal release notes as a metric discontinuity rather than a regression.
Sequencing.
on and confirm tenant and soft-delete global scopes are registered.^1.0.2 in a staging environment; clear caches.on is validated.Maintenance. Low. Patch releases, single maintainer-style cadence typical of 1.0.x packages. The operational burden is mostly ours: keeping global-scope registration correct as models evolve, since the package's correctness is inherited rather than enforced. Budget a small recurring review each time a new taggable model is added.
Support. No commercial support implied. Dependency risk is moderate — a pre-1.1 package with a security-adjacent fix history should be tracked in our dependency register with an owner and a review date. If the package stalls, the fallback is straightforward: the tagging schema is simple enough to fork or reimplement against our own query layer.
Scaling. The popular-tags endpoint is an aggregate count over the taggables pivot, filtered by type and model. At scale this is the hot path: counts are expensive relative to list queries and are typically cached. Two concerns: (a) aggregate query cost grows with tag volume and pivot size, so confirm indexes on the pivot and consider a materialized/cached count table; (b) scoped counts vary per tenant, so any cache key must include tenant identity — a shared cache key is a cross-tenant leak vector and would re-create exactly the bug 1.0.2 fixes.
Failure modes.
on.on: arbitrary class resolution if the parameter is passed to the container or query builder unfiltered. Mitigation: middleware-level validation.composer.json and treat rollback as a security decision, not a routine one.Ramp-up. Low for a Laravel-fluent engineer — a day to integrate, half a day to validate scoping and routes. The non-obvious learning is the scope-inheritance model: engineers must understand that the package is only as tenant-safe as the global scopes we register, and that this release moved that contract from implicit to load-bearing. Document that in the integration README so it survives team turnover.
How can I help you explore Laravel packages today?