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

Many To Many Matrix Bundle Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

Read this first: the package does not fit a Laravel codebase. yokai/many-to-many-matrix-bundle is a Symfony bundle built around Symfony Forms and Doctrine ORM. It has no Laravel service provider, no Blade views, and no Eloquent integration. Pulling it into a Laravel app gets you a dependency graph of Symfony Form/Doctrine packages and nothing that renders. Treat this assessment as "what the package does, and how to get the same behaviour in Laravel" rather than "how to install it."

What it actually does (Symfony context). It registers a custom form type that renders a Doctrine ManyToMany association as a two-dimensional checkbox grid — rows from one side of the relation, columns from the other — instead of the default EntityType multi-select or expanded checkbox list. Instead of picking from a flat list of 200 permissions, you see a matrix of roles × permissions and tick cells.

If you are genuinely in a Symfony 2.x/3.x app:

  1. composer require yokai/many-to-many-matrix-bundle
  2. Register the bundle in AppKernel::registerBundles() (or config/bundles.php on Symfony 3.4+ Flex, if it works at all).
  3. Register the bundle's Twig form theme in config.yml under twig.form_themes — the matrix layout almost certainly lives in a Twig block, and without the theme you get a plain expanded checkbox list.
  4. Build a form with the bundle's matrix type, pointing it at the association's target entity.

Where to look first: the Form/Type/ directory for the type class (getName() / getBlockPrefix() tells you what to pass to ->add()), then Resources/views/Form/ for the theme block, then the README example — for a 9-star 2016 bundle, the README is the documentation.

First use case: a classic admin screen — Role ↔ Permission, or Product ↔ Category, where an editor assigns many-to-many links by clicking cells. This is the sweet spot; it's the only thing the package was built for.

Laravel equivalent of "getting started": you already have the primitives. belongsToMany on both models, a pivot table, a Blade view with <input type="checkbox" name="permissions[]" value="{{ $permission->id }}"> laid out in a <table>, and $role->permissions()->sync($request->input('permissions', [])). You can build the first version of this in under an hour, which is the strongest argument against trying to port the bundle.

Implementation Patterns

Symfony workflow this bundle assumes. You load the owning-side entity with its collection, build a form whose matrix field maps to that collection, render the grid, and on submit let Doctrine's collection semantics attach/detach by diffing the submitted set against the loaded one. Persist and flush; the owning side must be the one the form binds to, or nothing is written.

The Laravel translation, pattern by pattern:

  • Grid rendering. Two queries: $roles = Role::orderBy('name')->get() for rows, $permissions = Permission::orderBy('name')->get() for columns. Pivot the inner loop so cells are $role->permissions->contains($permission->id) pre-checked. Eager-load permissions on the rows to avoid N+1 in the checkbox loop.
  • Submission and diffing. sync() is the direct analogue of Doctrine's collection diff — it attaches new IDs, detaches missing ones, and leaves untouched rows alone. Use syncWithoutDetaching() only if you have a separate "add links" flow; for a full-grid save screen, plain sync() is correct.
  • Validation. Never trust the array blindly. 'permissions' => ['array'], 'permissions.*' => ['integer', 'exists:permissions,id']. This is the equivalent of Symfony's EntityType constraint doing the lookup for you — Eloquent will not validate IDs for you.
  • Transaction + authorization. Wrap the multi-row sync in DB::transaction() when a save touches several rows at once, and check a policy per cell ($this->authorize('assign', $permission)) if different permissions are grantable by different admins. The bundle has no concept of per-cell authorization; neither does the naive Laravel version.
  • Large matrices. A 50×50 grid is 2,500 checkboxes and a ~100KB payload. Chunk the columns into tabs or paginate them, or switch to a per-row edit screen once the Cartesian product exceeds a few hundred cells. This is a hard limit of the matrix UI, not of either implementation.

Integration tips. If you want the reactive feel without page reloads, Livewire handles this well — wire:model="selected" on each checkbox, sync() on save. For an API-first app, skip the matrix form entirely: a PUT /roles/{role}/permissions accepting {permission_ids: [...]} plus sync() is the same operation with less UI surface.

Gotchas and Tips

Package risk dominates everything else. Last release 2016-05-31, 9 stars, 0 dependents. That is an unmaintained Symfony 2/3-era bundle: no Symfony 4+ Flex recipe, no PHP 7.4+ / 8.x guarantee, no security patches, no community to answer questions. Even in a Symfony app I would copy the Twig block and the type's logic into my own codebase rather than take the dependency. In a Laravel app the question is moot.

Doctrine-side pitfalls (if you inherit a Symfony app using it): the form must bind to the owning side of the association or writes silently no-op; collections of entities need by_reference => false on the field, or Symfony mutates the array in place and Doctrine never sees the change; and orphanRemoval on a many-to-many is a footgun — it deletes the related entity, not just the link.

Laravel-side pitfalls that mirror the same bugs:

  • Empty submission detaches everything. sync([]) wipes all links. If the grid is paginated or tabbed, a partial form will silently delete the links on the pages you didn't render. Submit the full set, or carry hidden inputs for untouched rows, or write a diff server-side against the current collection.
  • Mass assignment. $request->all() into sync() is how you get privilege escalation on a permission grid; always $request->validate() first and pass $validated['permissions'] explicitly.
  • Missing @csrf. A grid of checkboxes is still a form; the 419 is the most common "it doesn't save" cause.
  • N+1 on the checkbox loop. $role->permissions->contains() without eager loading fires a query per cell. Role::with('permissions')->get().
  • Checkbox checked state drift. Use old('permissions', $role->permissions->pluck('id')->all()) so a validation failure doesn't reset the grid.

Debugging. In Symfony, the form profiler panel tells you whether the field bound and what was submitted; a form theme not applying is almost always a missing form_themes entry or a block-prefix mismatch. In Laravel, dd($request->input('permissions')) before sync(), and enable DB::listen() to confirm the pivot insert/delete statements actually fire.

Extension points worth stealing. The bundle's one genuinely good idea is separating the layout (Twig block / Blade partial) from the data mapping (collection diff). Keep that split: one Blade partial that renders a grid given rows, columns, and a selected-ID set, reusable across every many-to-many admin screen in your app. That partial is the part worth reimplementing, and it's about 30 lines.

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.
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log
hsluv/hsluv
seampact/codegen
seampact/hooks
seampact/ir
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk