kematjaya/base-controller-bundle
Reusable base controller classes for Symfony/PHP applications, helping reduce boilerplate around common CRUD, form, and service workflows. Extend the provided controllers to standardize actions and keep application code lean.
Start by reading the bundle's src/ directory and its README.md — this is a tiny, low-traffic package (3 stars, no dependents), so the source is the documentation. Look for:
AbstractCrudController or similar).DependencyInjection/ and Resources/config/ for what services and parameters get registered automatically.composer.json for the supported Symfony/PHP versions — pin accordingly before installing.Install it and register the bundle (Symfony Flex may or may not auto-register given the package's age; check config/bundles.php):
composer require kematjaya/base-controller-bundle
First use case: take one existing, boring CRUD controller (list + show + create + edit + delete for a single Doctrine entity) and rewrite it to extend the bundle's base controller. Keep the original alongside it until you've diffed behavior — this is the fastest way to learn exactly which conventions (route names, template paths, redirect targets) the base class assumes.
Expect to write a thin concrete controller:
class ProductController extends AbstractCrudController
{
protected function getEntityClass(): string { return Product::class; }
protected function getFormType(): string { return ProductType::class; }
protected function getTemplatePrefix(): string { return 'product'; }
}
…and then compare what routes/actions appear versus your hand-written version.
Thin controllers, fat base. The whole point of this pattern is that your concrete controllers become declarative: you supply the entity, form type, and template prefix, and inherit the index/show/new/edit/delete actions. Adopt this uniformly across entities so every CRUD screen behaves the same way — consistency is the only real payoff for a package this small.
Override, don't fork. When an entity needs special behavior (custom query for the index, a filtered repository, a different redirect after save), override the single protected method rather than copying the whole action. Common override points to look for and lean on:
getRepository() / query builder used for the index listing (add filters, ordering, soft-delete scoping).getRedirectRoute() after create/edit.getFormOptions() to pass entity-specific options.Template convention. Base controllers almost always infer templates from the prefix (e.g. product/index.html.twig, product/_form.html.twig). Standardize your Twig layout early: create the expected template tree under templates/ before wiring controllers, so you get immediate feedback on which variables the base actions pass.
Integration with security. Annotate or attribute your concrete controllers (#[IsGranted('ROLE_ADMIN')] on the class, per-action overrides where needed). Because the actions live in the parent, class-level security is usually the cleanest layer — but verify the base actions aren't declared final in a way that blocks attribute inheritance.
Flash messages and PRG. Most such bundles implement post/redirect/get with flash messages. Centralize flash handling in your base layout so both bundle-driven and custom controllers render them identically.
Testing. Since the actions are inherited, an integration test suite that hits each route once per entity gives outsized value: it catches regressions in the base class's assumptions (route names, form field names, redirect targets) that unit tests won't.
composer.json, and be prepared to fork or copy the base class into your app if a Symfony upgrade breaks it. Do not build your architecture around it without a fallback.$this->getDoctrine(), @Route annotations instead of attributes, ContainerAware — before assuming compatibility with a current Symfony release.final. Some base controllers mark actions final to force the override-method pattern. If you find yourself unable to change one action, that's the reason — work through the designated extension points instead of fighting it.flush() and redirect. If you add listeners or lifecycle callbacks that expect a second flush, verify the order. Debug with $em->getUnitOfWork()->getScheduledEntityUpdates() when saves seem to be silently skipped.createForm() wholesale is a common shortcut that drops options the base class passes. Override the narrowest hook available.config/packages/*.yaml tree, run bin/console config:dump-reference <bundle_alias> and debug:container --tag=... to see what's actually registered — small bundles frequently ship config that's never read.vendor/).How can I help you explore Laravel packages today?