yokai/many-to-many-matrix-bundle
Symfony bundle that displays many-to-many relations as an editable matrix grid, letting admins toggle associations in bulk from a single screen. Configure entities, fields, and labels to manage complex mappings without custom forms. Ideal for admin panels.
Architecture fit — poor, and the framing needs correcting up front. Despite being a PHP package, this is not a Laravel package. It is a Symfony bundle (yokai/many-to-many-matrix-bundle) built around the Symfony Form component, Doctrine ORM's ManyToMany mapping, and the Symfony DI container/kernel bundle registration model. Laravel's equivalents — Eloquent BelongsToMany, Blade/Livewire, service providers — share no interfaces with it. There is no adapter seam to bridge: you cannot composer require this into a Laravel app and boot it, because doing so drags in a second ORM and a second HTTP kernel's worth of assumptions. The reusable asset here is the pattern, not the code.
The pattern itself is narrow and well understood: render an owning-side ManyToMany join as an R×C checkbox grid, where row and column headers are the two entity sets and each cell toggles a join row. Laravel already solves the persistence half of this natively via sync() / syncWithoutDetaching() on a BelongsToMany relation. Only the grid UI is missing, and that is a Blade partial or a Filament CheckboxList with a grid layout — not a dependency.
Integration feasibility — effectively zero for direct adoption. Symfony 2/3-era form types (AbstractType::getName(), choices_as_values, templating helpers) were removed or changed in later majors. Composer will fail to resolve its symfony/form constraint against Laravel's symfony/* ^6/^7 transitive set on any modern install. The only viable path is vendoring it as MIT-licensed reference material and porting the rendering logic.
Technical risk — high, concentrated in abandonment. Last release 2016-05-31, 9 stars, 0 dependents. That combination means no maintainer, no issue triage, no security patching, and no community signal that the abstraction survives contact with real schemas. A dependency with zero dependents is unvalidated by definition; you would be the first production user of a nine-year-old artifact built for a framework generation Laravel no longer shares components with.
Key questions before any work is scheduled:
sync() is already the answer and this package is irrelevant.BelongsToMany field (Filament, Nova, Backpack) that renders multi-select with search? If so, the gap is cosmetic, not functional.Stack fit. Eloquent BelongsToMany + sync() covers persistence, pivot-table diffing, and detach-on-missing-key semantics out of the box. The matrix becomes a presentation concern: a Livewire component or Filament form field that renders the grid, binds to wire:model, and hands a flat array of selected IDs to sync(). No Doctrine, no bundle registration, no second container.
Migration path. Treat the repository as a specification document. Read the form type and its Twig template to extract the behavioral contract — header derivation from each side's collection, checked-state resolution from the owning entity, and the submitted payload shape (typically matrix[rowId][columnId]). Rebuild that contract natively against Blade + a form request. Do not fork the bundle into the application: forking preserves the Symfony/Doctrine baggage without preserving the maintainer, which is the worst of both outcomes.
Compatibility. Two hard blockers, both structural rather than version-pin annoyances. First, symfony/form ^2/^3 will not co-resolve with a modern Laravel symfony/* tree. Second, running Doctrine ORM alongside Eloquent means two connection managers, two migration systems, two identity maps, and two transaction boundaries over what is almost certainly the same database. That is an operational liability no UI feature justifies.
Sequencing.
sync(); explicitly decide partial-update semantics (see failure modes below).FormRequest with per-axis exists rules and an authorization policy — the matrix posts a large array of foreign keys and is a mass-assignment surface.Maintenance. Zero upstream maintenance is available, so any adoption converts a dependency into a permanent in-house fork. Porting the pattern instead yields roughly a hundred lines of view and component code that the team already understands and can change without reading a nine-year-old Symfony changelog.
Support. No maintainer, no active issues, no documentation beyond a minimal README. Any production incident debugs to source code you wrote or vendored, which is acceptable if you own it deliberately and unacceptable if it arrives as a transitive dependency.
Scaling. The matrix is the scaling risk, not the package. Every cell is an input element: 50×50 is 2,500 controls, 100×100 is 10,000, and the HTML payload, browser render, and POST body all grow quadratically. Past a few hundred controls the UX degrades into scroll-hunting with no search affordance. If either axis is unbounded or operator-controlled, the grid will not survive; design in filtering, pagination, or per-row submission from the start.
Failure modes.
max_input_vars truncation — PHP's default is 1,000. A large matrix exceeds it and the request is silently truncated, producing a partial sync() that detaches valid join rows. This is the single most likely production incident and it fails quietly. Raise the limit, or chunk submissions, and assert the expected cell count server-side.sync() treats absent IDs as removals. If the client submits only visible or filtered rows, hidden links are destroyed. Use syncWithoutDetaching() plus explicit removals, or scope the sync to the rendered subset.DB::transaction(); a mid-request validation failure leaves a half-applied matrix.Ramp-up. A Laravel developer can reproduce this pattern in one to two days with no prior exposure. The high-cost path is the one where someone insists on Symfony/Doctrine knowledge transfer to interpret the bundle — that is a cost with no corresponding output.
Overall. At a 0.335 score with zero dependents and a 2016 release, this reads as a pattern reference, not a candidate dependency. The correct TPM action is to close the adoption question, cite the framework mismatch as the deciding factor rather than age or stars alone, and redirect the effort into a small native implementation that the team owns outright.
How can I help you explore Laravel packages today?