zk2/sps-bundle
zk2/sps-bundle is a Symfony bundle that packages SPS integration for PHP applications, providing configurable service wiring and a convenient starting point for Symfony projects that need SPS support. It aims to keep setup simple while fitting into standard bundle conventions.
The bundle has one star, zero dependents, and its last release was February 2021 — so treat the README as a starting hypothesis, not documentation. Your first moves:
composer require zk2/sps-bundle (check the README for the exact constraint; 2021 packages often predate Symfony 6, so you may need --with-all-dependencies and possibly a symfony/framework-bundle compatibility shim). Then open vendor/zk2/sps-bundle/ — the DependencyInjection/, Resources/config/, and Controller/ directories tell you more in five minutes than the README will.config/bundles.php if it isn't auto-discovered, and check for a Resources/config/services.yaml you need to import.abstract in Controller/ or a service tagged for table rendering. That class is your base.Orders with a search box, page links, and clickable column headers. Start there rather than wiring it into a complex multi-entity dashboard.If the bundle provides Twig templates under Resources/views/, override them in templates/bundles/SPSBundle/ so you're not editing vendor code.
Because the package is a thin SPS abstraction over Doctrine, expect the following shape in practice:
Subclass-the-controller pattern. Most likely you extend an abstract SPS controller and declare:
Keep these declarations in one method per resource so you can spot drift when you add columns.
// Illustrative — verify against vendor source
class OrderController extends AbstractSPSController
{
protected function configure(): void
{
$this->setQueryBuilder($this->em->getRepository(Order::class)->createQueryBuilder('o'));
$this->setSearchable(['o.reference', 'o.customerName']);
$this->setSortable(['o.createdAt' => 'desc', 'o.total' => 'asc']);
$this->setPerPage(25);
}
}
Search → Pagination → Sort pipeline. The bundle's whole value is composing these three: apply WHERE … LIKE from the search input, then setMaxResults/setFirstResult, then ORDER BY. When you add a custom filter (date range, status enum), inject it into the query builder before pagination is applied — otherwise your COUNT for the pager will be wrong.
Service-layer wrapping. Don't let the SPS config bleed across your controllers. Build a small TableQuery service per entity that takes a Request and returns a Paginator object, then keep the SPS controller a thin adapter. This makes the bundle easy to swap out if it goes unmaintained (which, given the release date, is a real risk).
Twig integration. If the bundle ships a table/pager template, use it for admin screens and override only the CSS classes. Keep your own pagination markup for user-facing pages — you'll want control over the markup and rel/canonical logic.
Sort whitelisting. Always whitelist sortable columns against the config; never pass the sort query parameter straight into addOrderBy (SQL injection and unindexed-sort DoS). The bundle likely does this for you, but verify addOrderBy is only called with declared fields.
config/bundles.php and wire services in config/services.yaml.Attribute autoconfiguration and the FrameworkBundle route-loader changes. Check composer.json in the vendor dir for symfony/* constraints; you may need symfony/framework-bundle:^5.4 or a ContainerBuilder compatibility patch.COUNT(*) for the pager. If you join relations for search, the count returns inflated totals — use ->select('COUNT(DISTINCT o.id)') or set ->distinct() explicitly in your configure() after the joins.->addOrderBy('o.id', 'ASC')). Without it, rows shuffle between pages when the primary sort column has ties — a classic "why is this row on page 2 and page 3?" bug.q= parameter: it should be a no-op (LIKE '%%' on a large table is a full scan even when it "matches everything"). Guard with if ($q !== null && $q !== '') in your query-building code.Paginator + Doctrine\ORM\Tools\Pagination\Paginator is usually cheaper than maintaining a fork.How can I help you explore Laravel packages today?