fsi/datasource-bundle
FSi Datasource Bundle integrates the FSi DataSource component with Symfony, providing a reusable abstraction for paginated, filterable, and sortable listings backed by Doctrine or custom sources. Ideal for admin grids, search results, and API collections.
This is a Symfony bundle, not a Laravel package — it ships an Extension/CompilerPass and a Twig extension, so there is no service provider, facade, or config publish step you'd recognise. Treat it as Symfony-only glue around the framework-agnostic fsi/datasource component.
Minimal steps:
composer require fsi/datasource-bundle
Register the bundle the way your Symfony version expects:
// Symfony 3.x / 4.x (AppKernel.php)
$bundles = [ /* ... */ new FSi\Bundle\DataSourceBundle\FSiDataSourceBundle() ];
// Symfony 4.4+/5.x (config/bundles.php)
return [ /* ... */ FSi\Bundle\DataSourceBundle\FSiDataSourceBundle::class => ['all' => true] ];
Where to look first, in this order — the README is thin, so read the code:
DependencyInjection/Configuration.php — the actual config surface (likely a driver key). If it's empty, there's nothing to configure and the bundle is pure wiring.Resources/config/services.xml (or *.yaml) — the real service IDs; don't guess them, they are the contract you'll inject.Twig/ — the extension class tells you the exact function names and signatures for rendering a datasource in a template.First use case: a controller builds a datasource from a QueryBuilder, hands it to Twig, and the template renders it through the bundle's Twig function rather than a hand-rolled foreach.
public function listAction(DataSourceFactory $factory) // fsi_datasource factory service
{
$datasource = $factory->createDataSource('doctrine', [
'query' => $this->getDoctrine()->getRepository(Article::class)->createQueryBuilder('a'),
'fields' => ['a.title', 'a.createdAt'],
]);
return $this->render('articles/list.html.twig', ['datasource' => $datasource]);
}
Then in the template, use whichever helper the extension registers (commonly datasource_render / fsi_datasource_render — confirm with bin/console debug:twig | grep -i datasource).
Expect roughly 20 minutes to first render if the bundle is Symfony-3/4-era compatible with your app; expect an afternoon if you're on Symfony 6 and need to shim the bundle registration.
Custom datasource classes. The bundle's value is registration, not implementation — your real work is writing datasources. Subclass the component's abstract base (or implement DataSourceInterface) and implement getData() to translate the current filter/sort/page state into your query. If the bundle exposes a tag for datasources, register with it; otherwise instantiate explicitly through the factory.
Prefer the factory over new. A datasource is stateful per request (filters, pagination, sorting). The factory service exists so you get a fresh instance and so driver/type selection stays centralised. Never inject a constructed datasource as a singleton — you'll leak state between requests in long-running workers and between two usages on the same page.
Doctrine pattern. Wrap a QueryBuilder and let the datasource own the where/orderBy/setMaxResults mutation from its own state. Keep the repository query builder untouched until the datasource runs — clone it if the same builder is reused elsewhere in the request.
Twig-first rendering. The bundle's headline feature is that a datasource is renderable. Structure your templates so the datasource is passed in and the bundle function handles the loop, and keep the per-row markup in a small partial so the same datasource can render in a table view and a card view.
{# list.html.twig #}
{{ datasource_render(datasource, 'articles/_row.html.twig') }}
Events as the extension seam. The component fires pre/post data-retrieval events. Use a subscriber to add cross-cutting behaviour — caching, logging of query counts, injecting an owner/tenant filter — instead of editing each datasource:
public function preGetData(DataSourceEvent $event): void
{
$event->getDataSource()->setData(/* transformed */);
}
Be careful to guard against re-entrancy if your subscriber calls getData().
Keep controllers thin, put assembly in a service. A DataSourceProvider-style service per screen keeps the createDataSource(...) array config testable and lets you reuse the same datasource across a controller action, an API response, and an export command.
Maintenance risk is the headline. Last release September 2021, 5 stars, zero dependents. Treat it as unmaintained: pin the exact version in composer.json, commit composer.lock, and budget for a fork. The good news is that it's a thin bundle — everything of substance lives in fsi/datasource, so replacing the bundle with ~50 lines of your own services.yaml plus a Twig extension is a realistic escape hatch if you hit a Symfony incompatibility.
Symfony version mismatch is the likely blocker. A 2021 bundle will target Symfony 3/4/5. On Symfony 6+ you'll hit deprecated bundle base classes and possibly ContainerAware usage. The underlying component may still work; the wiring may not. Verify before promising a timeline.
Service IDs are undocumented. Run bin/console debug:container --show-private | grep -i datasource and bin/console debug:twig first — the README doesn't enumerate them and guessing produces confusing "cannot autowire" errors.
Twig extension not loaded. If the function is undefined, debug:twig will show it. If it's absent entirely, the bundle isn't registered for the current environment (config/bundles.php ['all' => true] vs a dev-only key).
Statefulness bugs look like data bugs. Symptoms: pagination page 2 shows page 1's rows, two datasources on one page interfere, filters bleed across a sub-request. Root cause is almost always a shared instance — inject the factory, not the datasource.
Repeated getData() calls mean repeated queries. Events fire each time. Cache the result on the datasource or memoize in your subscriber.
Debugging: bin/console debug:container, bin/console debug:twig, and the Symfony profiler's Doctrine panel (if Doctrine-backed) are where you'll spend your time. There's no logging inside the bundle.
Extension points, in order of usefulness: (1) custom datasource classes, (2) event subscribers on retrieval, (3) decorating the factory service to inject your own defaults, (4) your own Twig function in a separate extension if the bundle's renderer doesn't fit your markup. Forking the bundle itself should be last — the surface is small enough that owning it directly is often cheaper than fighting it.
If you're genuinely on Laravel: skip this bundle. Use fsi/datasource directly, bind the factory as a singleton in a service provider, and expose a Blade directive or a small @datasource($datasource, 'partial') wrapper around view(). You lose Symfony DI conventions, which is most of what this bundle provides.
How can I help you explore Laravel packages today?