qafoolabs/framework-extra-bundle
QafooLabs FrameworkExtraBundle adds a @Transactional annotation to Symfony controllers, letting you wrap controller actions in Doctrine database transactions. It simplifies transaction handling, supports read-only transactions, and keeps controller code clean by moving transaction boundaries into an...
Read this first: qafoolabs/framework-extra-bundle is a Symfony 2.x-era bundle (repo: QafooLabsNoFrameworkBundle) that is archived, has zero dependents, and has nothing to do with Laravel. You cannot composer require it into a Laravel app and get behaviour — there is no service provider, no facade, no Laravel integration of any kind. If you are evaluating it from a Laravel codebase, you are evaluating ideas, not a dependency.
Minimal steps if you are actually on a legacy Symfony 2.x app:
composer require qafoolabs/framework-extra-bundle — but check the version constraint against your Symfony version first; the archived tags top out well before Symfony 4.app/AppKernel.php (class lives under QafooLabs\Bundle\NoFrameworkBundle\). Place it after the framework bundle so its controller resolver can override the default one.php app/console cache:clear) — the bundle swaps core services, so a stale container is the #1 source of "nothing happened".Where to look first, in order:
Controller/ — the controller resolver. This is the whole point of the bundle: resolving controllers out of the container and resolving action arguments by type-hint.View/ — the View value object plus its kernel listener, which turns a returned View into a Response (template, data, status code).DependencyInjection/ — to see exactly which core Symfony services get decorated/replaced.Tests/ — the README is thin and the bundle is old; the test suite is the only reliable specification of behaviour.First real use cases, in the order most people hit them:
// 1. Controller as a plain service, no base class
class ArticleController
{
private $repo;
public function __construct(ArticleRepository $repo) { $this->repo = $repo; }
// 2. Action arguments resolved by type-hint / request attribute
public function showAction(Request $request, Article $article)
{
return new View(['article' => $article], 200, 'Acme:Article:show.html.twig');
}
}
Routing then references the service, not a Bundle:Controller:action string. That's the day-to-day win: constructor injection everywhere, no $this->get('...') in controllers.
Controller-as-a-service, everywhere. Define controllers as services and reference them in routing by service id. This is the pattern the bundle exists to enable, and it is the one thing worth copying conceptually. In Laravel the equivalent is already the default: type-hinted constructor and method injection on controllers resolved by the container, so you get this for free without a package.
Action argument injection. The resolver inspects the action signature and fills arguments from (a) the container by type-hint, (b) request attributes by name, (c) the Request itself. The day-to-day pattern is thin actions:
public function editAction(Article $article, FormFactory $forms, Request $request) { ... }
Keep the parameter list short and ordered "domain object first, framework objects last" — it reads better and makes the resolver's fallback order predictable when you debug.
Return a View instead of hand-building a Response. Instead of new Response($twig->render(...)), return new View($data, $status, $template). The listener handles rendering. This is the bundle's answer to Laravel's view() / response()->json() helpers. Pattern: controllers always return either a View or a Response; never echo, never exit.
Attribute-to-argument conversion. Use request attributes (set by routing defaults or a listener) to feed typed arguments — this is a poor-man's route-model binding. If you are porting the idea to Laravel, do not port the mechanism: use explicit route-model binding (Route::get('/articles/{article}', ...)) plus a findOrFail, which is typed, explicit, and testable.
Integration tips.
sensio/framework-extra-bundle's ParamConverter on the same argument. Two resolvers claiming the same type-hint produces order-dependent, silently-wrong behaviour.debug:container controller_resolver (or debug:container --show-private on newer 2.x).qafoolabs/framework-extra-bundle is not sensio/framework-extra-bundle. A composer require framework-extra-bundle will pull Sensio's package, and any docs/snippets you find online will be for the wrong bundle. Always use the full vendor name.$x argument" at runtime, not at container-compile time. Debug by dumping $request->attributes->all() and by checking that the type-hint's service is actually registered and public.null falls through, one that throws stops everything. If you add your own resolver, register it after the built-ins and make it return null (not throw) for "not mine".services.yml without a cache:clear gives confusing behaviour where the old resolver is still active in prod but the new one is used in dev.view()/Responsable for View-like returns, and explicit route-model binding instead of attribute conversion. The one idea worth stealing is forcing controllers to be plain services with no framework base class; enforce it with a code-review rule or a Pest/PHPStan check rather than a package.composer.json (there will be no new tags), vendor the source if Packagist availability is a concern, and plan a migration to plain Symfony controllers + sensio/framework-extra-bundle (or Symfony 5+ native argument resolution) as the exit path.How can I help you explore Laravel packages today?