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

Bonsai Laravel Package

baril/bonsai

Closure Table pattern implementation for Laravel Eloquent that lets you store and query tree-like structures in a relational database. Provides fast ancestor and descendant lookups across all supported DBMSs, migration artisan commands, and an alternative to nested sets.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit. Bonsai implements the Closure Table pattern as a drop-in Eloquent concern (Baril\Bonsai\Concerns\BelongsToTree). This is a well-understood pattern for hierarchical data and a strong architectural match when the dominant access pattern is read-heavy, depth-agnostic traversal: ancestor/descendant lookups, subtree extraction, breadcrumb rendering, permission inheritance, category trees, org charts. It avoids the pathological query cost of recursive adjacency-list walks and the whole-table rewrite cost of nested sets on insert. The integration contract is minimal and idiomatic for Laravel—a parent_id column on the main table, a companion closure table, and a trait—so architectural surface area added to a host application is small. The design choice that the closure table is derived data (the parent_id remains canonical) is the right one: it gives us a reconciliation primitive (bonsai:fix) instead of a single point of corruption.

Integration feasibility. Low friction. The onboarding path is three steps: composer require, add the trait, run bonsai:grow to generate the closure migration, then bonsai:fix to backfill closures for pre-existing rows. Relationship ergonomics (ancestors, descendants, siblings, parent, children) map directly onto typical product requirements, and the query scopes (descendantsOf, ancestorsOf, onlyRoots, withDepth, withHeight) cover most traversal needs without raw SQL. The read-only guard on ancestors/descendants and the TreeException on cycle creation are good defensive defaults—they fail loudly at the boundary rather than silently corrupting the tree.

Technical risk. The dominant risk is not technical but supply-chain and longevity: 36 stars, 0 dependents, and a single maintainer. That is a bus-factor of one. The package is nonetheless better-instrumented than most in its weight class—tests, coverage badge, generated API docs, changelog, and a clear Laravel compatibility matrix through 13.x—which mitigates but does not eliminate the abandonment risk. Secondary risks:

  • Version churn. 3.4 was a substantial internal rewrite with a wave of deprecations (getTree(), withAncestors(), BelongsToOrderedTree, the --migrate flag) explicitly slated for removal in v4. We are integrating against an API with a known expiry horizon.
  • Write amplification. Every insert/move/delete touches multiple closure rows proportional to depth. Fine for shallow trees; a real cost for deep ones or bulk imports.
  • Closure table growth. Storage is O(n × depth), not O(n). Must be sized deliberately.
  • Transaction semantics. bonsai:fix truncates and rebuilds the closure table; on a large table that is a maintenance-window operation, not a hot-path repair.
  • Soft-delete restore coupling. Restoring a node whose parent was deleted throws; the documented workaround (cut()->restore()) is a behavior we must encode into product logic, not just infrastructure.

Key questions.

  1. What are our actual tree cardinality and depth characteristics (n and average/max depth)? This determines both storage cost and write amplification.
  2. What is the read:write ratio on the hierarchy? Closure tables are a poor fit for write-heavy, churn-heavy trees.
  3. Do we require sibling ordering? If so, we are implicitly adopting a second single-maintainer package (baril/orderly) and doubling our dependency risk.
  4. What is the maintainer's responsiveness record on issues and Laravel major upgrades? The 3.5 release (Laravel 13) suggests active upkeep, but we should verify recency and issue latency.
  5. What is our exit strategy if the package is abandoned—fork, vendor, or migrate to kalnoy/nestedset/staudenmeir/laravel-adjacency-list?
  6. Does the domain need multiple independent trees (forest) and how are roots disambiguated? (findCommonAncestorWith returning null across roots is a real product edge case.)

Integration Approach

Stack fit. Bonsai is a native Eloquent extension, so it introduces no runtime, no service, and no new infrastructure dependency—this is the cheapest possible integration shape for a Laravel service. It requires only that the host model be a standard Eloquent model and that the database support the closure migration. From v3.3 onward all Eloquent-supported DBMSs work (MySQL, PostgreSQL, SQLite, SQL Server), so we are not constrained to MySQL the way pre-3.3 adopters were. If we are on Laravel 10+, we also get the withHeight() scope.

Migration path. For a greenfield table, the path is trivial: trait + bonsai:grow + migrate. For an existing adjacency-list table, the sequence is: add the parent_id column if missing → run bonsai:grow → migrate → bonsai:fix to backfill. The backfill is the risky step and should be treated as a data migration with a defined window, not a deploy-time command. For a nested-set incumbent, the migration is more involved: we would need an ETL step that reconstructs parent_id from lft/rgt and then runs bonsai:fix.

Compatibility. Version pinning must be explicit against the Laravel matrix: 3.5+/Laravel 13, 3.3+/12, 3.2+/11, 3.1+/10, 3.x/9. Because the package tracks Laravel majors closely, our upgrade cadence for Laravel now has a transitive dependency: a Laravel major bump requires a Bonsai minor bump in the same window. That should be captured in the platform upgrade runbook. We should pin with a caret constraint on the minor and test the upgrade explicitly.

Sequencing.

  1. Pilot (1 sprint): apply Bonsai to a single low-blast-radius, shallow, read-heavy tree (e.g., content categories or a taxonomy) to validate ergonomics, query shapes, and the migration story against real data volume.
  2. Instrument (1 sprint): add observability—query counts on descendants eager-loads, closure table row counts, and a scheduled bonsai:show sanity check. Establish a baseline for closure-table growth.
  3. Harden: codify the TreeException handling into domain code (cycle prevention at the application layer, not just the package layer), wrap deleteTree/forceDeleteTree in explicit confirmation flows, and define the bonsai:fix runbook.
  4. Expand: roll out to additional models only after the pilot proves the write patterns.
  5. Defer ordering: treat baril/orderly as a separate decision with its own risk review.

Operational Impact

Maintenance. Ongoing maintenance is light in the steady state—no daemons, no migrations beyond the initial closure table, no configuration. The real maintenance burden is upgrade choreography: every Laravel major requires a corresponding Bonsai bump, and the announced v4 deprecations mean we will face a breaking-change migration in the next 12–18 months. Budget for that as planned technical debt rather than an emergency. Because the package is small and readable, forking is a credible fallback if the maintainer goes dark, but a fork becomes our code to own—factor that into the cost model.

Support. There is no commercial support and no SLA. Support is GitHub issues + the generated API docs + the README. The documentation quality is genuinely above average for a package of this size (full API reference, changelog, migration matrix, worked examples for soft deletes and ordering), which materially reduces the support burden. But escalation path is "read the source," which is acceptable given the codebase is small and we should plan for at least one engineer to be able to read it.

Scaling. Reads scale well—descendant and ancestor queries are index-friendly joins against the closure table, and eager-loading descendants auto-hydrates the recursive children relationship without additional queries, which is a meaningful N+1 avoidance win. Writes scale linearly with depth, not with table size: a move or insert touches O(depth) closure rows. The failure envelope is therefore deep or extremely wide trees with high mutation rates. Concretely: subtree moves (graft/graftOnto) are the most expensive operation because they rewrite closure rows for the entire moved subtree; bulk imports should bypass the ORM or batch carefully. Storage scales as O(n × depth) and should be monitored; a capped depth (e.g., ≤ 5–7 levels) keeps this comfortably cheap.

Failure modes.

  • Closure corruption (drift between parent_id and the closure table) — the highest-severity failure. Mitigated structurally by the design (closures are derived) and operationally by bonsai:fix, but bonsai:fix truncates and rebuilds, so it is a maintenance-window recovery, not a live repair. We need a scheduled integrity check, not just a recovery tool.
  • Cycle creation — blocked at save time by TreeException, but only if all writes go through the model. Any raw SQL writer bypasses the guard; enforce model-only writes as a code-review invariant.
  • Delete of a node with children — throws TreeException by default. That is correct and safe, but it means product delete flows must explicitly choose between delete() (refuse) and deleteTree() (cascade). This is a product decision that must be surfaced in the UI, not swallowed in a catch block.
  • Soft-delete restore with deleted parent — throws; requires the cut()->restore() pattern. This will surface as a confusing user-facing error if not handled deliberately.
  • Backfill on large existing tables — bonsai:fix over a large dataset can be slow and lock-heavy. Must be scheduled, batched, and rehearsed on a staging copy.
  • Upstream abandonment — degrades to a fork-or-migrate decision. The shallow integration surface (a trait plus a table) makes migration out tractable, which is the key mitigation.

Ramp-up. A competent Laravel engineer can be productive with Bonsai in under a day; the API is idiomatic and the README covers the 90% cases. The conceptual ramp-up is not about the package but about the closure table mental model—specifically the derived-data invariant and the write-cost profile. That understanding must be imparted to anyone touching the schema or writing migration/import jobs, otherwise someone will hand-write parent_id updates and silently desynchronize the tree. Recommend a short internal design note covering: canonical vs. derived data, bonsai:fix as the reconciliation primitive, the model-only-writes invariant, and the depth budget.

Bottom line. Bonsai is a well-built, well-documented, low-ceremony fit for read-heavy hierarchical data in a Laravel application, and the integration cost is genuinely small. It is not a fit if the tree is deep, mutation-heavy, or if the organization cannot tolerate a single-maintainer dependency for a schema-level concern. The mitigation is cheap—shallow integration means the exit door is open—but the decision should be made explicitly, with a pinned version, an upgrade runbook, and a scheduled integrity check, rather than adopted implicitly by a developer who liked the API.

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.
steadrun/laravel-monitor
labyrinthe/connect-laravel
elmogy/layan
drewdan/sentinel-client
dskripchenko/laravel-llm
fatryst/nuonuo
stevenay/myanfont
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc
animalyoung/laravel-influxdb
onix-solutions/sms-telera
lonely-lights/prosetta
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin