sylius/resource-bundle
SyliusResourceBundle provides format-agnostic CRUD controllers and persistence utilities for Symfony apps, built on Doctrine's ObjectManager and ObjectRepository interfaces. It simplifies listing, filtering, sorting, and displaying resources without duplicating controllers.
Install the bundle and register it:
composer require sylius/resource-bundle
// config/bundles.php
return [
// ...
Sylius\Bundle\ResourceBundle\SyliusResourceBundle::class => ['all' => true],
];
The fastest way to orient yourself is the official documentation at stack.sylius.com/resource (linked from the README) — the README itself only explains the philosophy, not the API. Then run the debug command to see what the bundle actually registered:
bin/console debug:resource
Pick one small entity as your first use case (a Book is the canonical example in the docs) and wire up the four moving parts:
sylius_resource.resources so the bundle generates the factory, repository, manager, and controller services:# config/packages/sylius_resource.yaml
sylius_resource:
resources:
app.book:
classes:
model: App\Entity\Book
repository: App\Repository\BookRepository
factory: Sylius\Component\Resource\Factory\Factory
controller: App\Controller\BookController
driver: doctrine/orm
AbstractController) and only override what you need:use Sylius\Bundle\ResourceBundle\Controller\ResourceController;
final class BookController extends ResourceController
{
// index / show / create / update / delete are inherited
}
Routing — expose the actions with the bundle's route loader (type: sylius_resource) or, on 1.11+, with operation attributes directly on the controller.
Templates — provide templates/book/index.html.twig etc. following the bundle's naming convention.
Once this single CRUD works, you have the pattern for every resource in the app.
Resource-per-feature configuration is the unit of work. Every resource you add is a self-contained YAML block that maps an entity to services and a controller. The naming convention for the generated services is <resource_name>.<role> — app.book.repository, app.book.factory, app.book.manager. Use those ids in your own services rather than constructing repositories yourself.
Thin controllers, fat operations. The controller is a dispatcher. Extend ResourceController for default CRUD and override a specific action only when the default flow doesn't fit. For anything more complex, the newer operation system lets you declare routes and handlers per action:
#[AsResource]
final class BookController
{
#[GetCollection]
public function indexAction(Request $request): Response { /* ... */ }
#[Post]
public function createAction(Request $request): Response { /* ... */ }
}
Note the attribute is AsResource, not Resource — the older name was renamed because Resource was a reserved-ish word.
Event hooks are the extension point for behaviour. Instead of overriding actions, listen to the lifecycle events (pre_create, post_create, pre_update, post_update, pre_delete, etc.) and keep controllers generic. This is also where the bundle exposes flash messages from processors (see "flash from event on processor" in the changelog) — the processor can emit a flash and the controller handles it, so your business logic doesn't have to know about Symfony's session.
Grids drive index pages. For list screens, configure a grid (filters, sorting, pagination) and mark the index operation as grid-aware, rather than hand-rolling query building in the controller. Grids are what turn the bundle from "a controller with a repository" into a real CRUD foundation — and they're the piece most teams underuse.
Multi-driver is a real feature, not marketing. Because everything is built on Doctrine's ObjectManager and ObjectRepository interfaces, the same controller and config work against ORM or ODM. If you're building something that might need a non-relational backend, stay on the ObjectManager APIs in your custom code so you don't paint yourself into a corner.
Templates and Twig helpers. The bundle ships Twig helpers for resource names and route generation (sylius_resource_name, path helpers). Use them so your templates stay portable when a resource's name or plural name changes — which is a config value, not a hard-coded string.
Format-agnostic controllers. The base controllers were designed to serve different presentations (HTML, JSON, XML) from the same logic. If you're building an API, implement a serializer/format layer rather than duplicating controllers — that's the original motivation for the bundle.
It is not an admin generator. The README is explicit: it won't create forms, filters, or grids for you. It gives you format-agnostic controllers plus basic sorting and filtering. If you want Sonata/EasyAdmin-style scaffolding, this is not that — plan for the extra work.
There is no main or master branch. Every PR targets the branch matching the minor version (1.10, 1.11). If you're forking or contributing, that's the first thing to know.
1.11 moved a lot of namespaces. The changelogs are dominated by "Move some namespaces on …" refactors. If you're upgrading from 1.10, expect controller, context, Doctrine, grid, factory, Twig, and metadata namespaces to have shifted. Run the upgrade and let the autoloader tell you what broke — don't rely on old snippets from the internet.
Route naming and conflicts. Deleting a resource used to collide with other generated routes; the delete path name was changed to avoid this. If you're overriding routes manually, check the generated names with bin/console debug:router | grep book before adding yours.
HTTP method handling. The bundle now registers POST on update, delete, and bulk delete (for HTML forms that can't send PUT/DELETE), and fixes the response status for safe methods. If you're writing a REST client or an api-tests suite, verify the actual method/status matrix rather than assuming textbook REST semantics.
Debugging: bin/console debug:resource <FQCN> shows what the bundle knows about a resource — useful when a controller can't find its repository or when a route isn't generated. This was improved in recent versions.
Grid limits on the request grid provider had a bug fixed in 1.11.2; if you're on an older patch and see pagination limit overrides not applying, that's the fix.
Configuration quirks: the resource name is a dotted key (app.book), and the bundle derives the singular/plural name from it — override those explicitly if your entity doesn't match the humanizer's guesses. Likewise, data providers and data processing can be disabled per operation; the docs added sections for both ("Disable providing data", "Disable processing data") precisely because the defaults are surprising when you want a read-only or side-effect-free operation.
Extension points worth knowing: custom factories (for domain-specific construction), custom managers, request handlers, and the state machine integration (you can configure a specific state machine component per resource, per the 1.11 changelog). The state machine hook is the cleanest way to add transitions without bespoke controller logic.
Adoption reality check: this bundle is the CRUD substrate underneath Sylius itself, so it's battle-tested by a very large application — but almost nobody uses it standalone, and its docs live beside the Sylius Stack docs rather than on the repo. Treat it as infrastructure: expect to read source for edge cases, and budget time for the 1.10 → 1.11 namespace migration if you're starting from an older install.
How can I help you explore Laravel packages today?