tomatophp/tomato-category
Tomato Category is a category management package for TomatoPHP/Laravel apps, offering nested, sortable categories with an admin interface, API endpoints, and easy integration for organizing content across your projects.
Architecture fit. tomatophp/tomato-category is best understood as a schema + trait + admin-resource bundle, not a framework component. It gives any Eloquent model a polymorphic categorizable relationship backed by a single categories table with a type discriminator (category / tag / type) and an optional parent_id for shallow trees. For a TPM, the architectural decision it encodes is: one taxonomy table, discriminated by type, attached polymorphically. That is a reasonable, low-cost design for flat-to-shallow classification, and it is a poor fit for anything resembling enterprise taxonomy — no nested sets, no term merge/split, no audit trail, no search-grade faceting. Fit is high inside the Splade/TomatoPHP admin stack and low everywhere else; outside that stack this is roughly a day of hand-rolled migrations and a model, which removes the entire buy case.
Integration feasibility. Mechanically simple: composer require, publish config and migrations, migrate, add the trait to your model, register the resource. Realistically this is a 1–3 day task including verification, not the half-day the description implies — the trait name, pivot column names, and type literal casing all need confirming against src/Traits, database/migrations, and src/Models before anyone writes dependent code. The publish-order trap (migrate before publish → silently empty schema) is a known failure that costs an afternoon if it hits a junior engineer.
Technical risk. The headline risk is ownership, not code. Zero stars, zero dependents, and a last release of June 2024 (v1.1.10, a changelog-only patch bump) mean no field-tested upgrade path and no community signal on framework compatibility. Secondary risks are concrete and cheap to mitigate: stringly-typed discriminator values (silent empty results on casing mismatch), missing onDelete('cascade') leaving orphaned pivot rows, absence of a morph map causing class-name refactors to orphan every association, and no cache invalidation for any tree you cache yourself. Blast radius is contained only if this stays off customer-facing and revenue-critical paths — that is a scoping constraint to state explicitly, not delegate to engineering judgment.
Key questions to close before committing:
spatie/laravel-translatable, or is per-locale labeling aspirational?Stack fit. Treat Splade/TomatoPHP as a hard precondition, not a preference. If the admin layer is Filament, Nova, or Backpack, stop here — those each have mature taxonomy plugins, and spatie/laravel-tags plus a resource is the better-supported bet. Adopting this package is never a reason to adopt the stack; it is a marginal-cost argument after the stack decision is made.
Migration path. Greenfield taxonomy is the clean case: define the vocabulary, seed idempotently with explicit slug + type, and build outward. Retrofitting an existing category or tag table is materially harder — you need a data migration into the polymorphic pivot, and a morph map enforced from day one (Relation::enforceMorphMap([...])) before a single pivot row exists, because adding it later orphans every association a class rename already touched. Design the exit path at the same time: put the package behind a thin repository or Taggable-style interface so controllers and views never call it directly. That converts a future rip-out from a rewrite into a contained change.
Compatibility. Pin the exact version ("1.x.y", not ^1.0) in composer.json. Verify the package's declared PHP/Laravel constraints against our supported matrix in the same PR that adds it, and record the tested combination in the repo. Publish the config and rebind models.category to our own subclass rather than editing vendor/ — that is the intended customization seam and the only thing that survives composer update at this release cadence.
Sequencing.
src/Models, database/migrations, src/Traits, config/, and the Filament/Http admin surface) — ~15 minutes, de-risks everything downstream.tags(), categories()) as model methods so where('type', ...) never leaks into controllers or Blade.whereIn subquery from the pivot for filtered listings, with/withCount on index screens to avoid N+1).Steps 1–4 are the real cost; step 5 is the part that looks like progress.
Maintenance. This is a fork-and-own decision, and the roadmap line item should read that way rather than "integrate a package." Budget a named owner, a vendored copy (or a pinned fork we control), and a periodic review date. MIT licensing makes the fork legal and cheap; single-maintainer abandonment makes it necessary, not optional.
Support. Assume no SLA: no ticket queue, no community answers, no security patching, no CVE process. The escalation path is internal. If a stakeholder asks "who do we call," the honest answer is "the engineer we assigned," and that answer needs a name in the plan.
Scaling. The polymorphic pivot is where performance degrades first. Index (categorizable_type, categorizable_id) and (type, slug) explicitly if the published migration doesn't. Prefer a whereIn('id', $idsFromPivot) subquery over whereHas on filtered listing pages, and withCount over loading collections when rendering counts. Any cached category tree is our responsibility to invalidate — wire an observer on our Category subclass to flush the cache key on save/delete, or staging will seed and then stare at a stale menu.
Failure modes to design against:
'Category' vs 'category' returns empty sets with no error. Mitigate with constants/enum.bootstrap/providers.php).Ramp-up. Low for a competent Laravel engineer — the package is small enough to read in full. The onboarding cost that actually matters is institutional: write down the pinned version, the tested PHP/Laravel matrix, the morph map, the type constants, the canary test, and the fork owner. That document is the deliverable that turns a zero-star package into something the team can operate without the person who installed it.
How can I help you explore Laravel packages today?