gyro/mvc-bundle
Gyro MVC Bundle provides a lightweight MVC layer for Gyro applications, adding routing, controllers, action handling, and view rendering. It helps organize PHP projects into clean models, views, and controllers with a bundle-based architecture.
1. Install and register manually, pinning the beta explicitly. This bundle is not in the Symfony Flex recipe index, so composer require gyro/mvc-bundle will not create a config file or bundles.php entry for you — and because the newest tag is v1.0.0-beta8, Composer will refuse to resolve it under default stability. Ask for the exact version:
composer require gyro/mvc-bundle:1.0.0-beta8
Then register it yourself:
// config/bundles.php
return [
// ...
Gyro\MvcBundle\GyroMvcBundle::class => ['all' => true],
];
Check vendor/gyro/mvc-bundle/composer.json first for the symfony/framework-bundle and PHP constraints. As of v1.0.0-beta8 the package requires PHP 8.0+ — PHP 7.4 support was dropped (PR #29). If any of your CI matrix, deploy images, or composer.json platform config still pin 7.4, you cannot use this release; stay on beta7 or resolve the platform constraint first.
2. Read four files before writing any code. With 16 stars and zero dependents there is no documentation ecosystem, so the source is the docs:
src/*Bundle.php — the class name you need for bundles.php.src/DependencyInjection/Configuration.php + the Extension class — this tells you the config alias (likely gyro_mvc) and every knob. Dump it: bin/console config:dump-reference gyro_mvc.src/DependencyInjection/*Extension.php / Resources/config/*.xml — what gets auto-registered. Pay attention to anything tagged kernel.event_subscriber, because those fire for every controller in the app, not just yours.src/Controller/ — the base controller or traits, which is almost certainly the reason you'd install this in the first place.3. First use case: stop maintaining your own AbstractController. The realistic day‑one win is replacing a copy‑pasted base controller (template guessing, view objects, forward/redirect sugar) with the bundle's version:
namespace App\Controller;
use Gyro\MvcBundle\Controller\AbstractController; // verify FQCN in src/Controller
class InvoiceController extends AbstractController
{
public function show(int $id): object
{
return $this->renderView('show', ['invoice' => $this->invoices->get($id)]);
}
}
Smoke-test it end to end with bin/console debug:router plus one functional test in KernelBrowser — the whole value proposition is behavioral, so assert the Response, not the service graph.
View objects over raw arrays. If the bundle ships a view/response-conversion layer (a kernel.view listener), return a small value object from your action and let the listener turn it into a Response. This keeps controllers thin and makes the conversion testable in isolation:
final class InvoiceView
{
public function __construct(public Invoice $invoice, public int $status = 200) {}
}
Keep the mapping between "what my domain returns" and "what the bundle converts" in one place — a thin adapter — so a bundle upgrade can't ripple into your business logic.
Let template auto-resolution do the boring 80%, override the rest. Convention-based guessing (InvoiceController::show → templates/invoice/show.html.twig) is convenient but brittle across refactors. Adopt a rule: rely on the convention, and when you must deviate, be explicit at the call site rather than renaming templates to satisfy the guesser.
One base class, thin controllers. Extend the bundle's controller to inherit the helpers, but keep services injected via constructor (or #[Autowire]/action arguments) and keep domain logic out of the controller entirely. A single-vendor base controller tends to accumulate "just one more helper"; the way to stay portable is to never let it become the place where logic lives.
Partial adoption. You do not have to extend the base class everywhere. Because the interesting behaviour lives in listeners and argument resolvers, you can register the bundle and continue using Symfony\Bundle\FrameworkBundle\Controller\AbstractController for legacy controllers. Start by applying it to one new bounded context, measure the diff, then decide.
Argument resolvers for route entities. If the bundle provides entity resolution from route parameters, use it for {id} params and keep an explicit findOr404 in the resolver — 404 semantics belong there, not scattered in actions.
Testing pattern. Everything here is request/response facing, so prefer functional tests (static::createClient(), assert status + content) over unit tests on the base class. Add one integration test per inherited feature you actually rely on, so an upgrade that silently changes template resolution fails loudly. Run the suite on the lowest PHP version you intend to support — beta8 raising the floor to 8.0 is exactly the class of change that only shows up in CI.
Treat it as vendored internal code. Sixteen stars, zero dependents, and still on beta8 means no Stack Overflow answers, no upgrade guide, and a bus-factor of roughly one. Pin the exact version in composer.json, read the commit log between releases before bumping, and wrap any behaviour you depend on behind your own thin class so you can replace it in an afternoon.
Beta means the changelog is the only changelog. beta8 contains exactly one line — "Remove PHP 7.4 Support" — but that pattern is the point: a release can change the platform floor without changing a line of runtime behaviour, and nothing in the tag name signals it (beta7 → beta8 looks cosmetic). Before every bump, diff composer.json (php, symfony/* constraints) as well as src/, and never let composer update float this package.
Global side effects are the main risk. Bundle-wide listeners (view conversion, template resolution, response filtering) apply to every controller — including EasyAdmin, API Platform, or a legacy bundle's controllers. Before enabling in production, inspect the dispatcher: bin/console debug:event-dispatcher kernel.view (and kernel.controller, kernel.response). If you see collisions, restrict via config or listener priority, or don't register those subscribers.
Config alias and service IDs are guessable but not guaranteed. Run bin/console config:dump-reference <alias> rather than copying YAML from a blog post; if the alias is wrong Symfony will silently ignore your whole config block, which is a classic half-hour of debugging.
Not-Flex means not-flex. No recipe → manual bundles.php entry, manual config file (config/packages/gyro_mvc.yaml), and bundle routes are not auto-loaded. If the bundle ships routes, import them explicitly in config/routes.yaml with the right prefix.
Override templates the Symfony way. Use templates/bundles/GyroMvcBundle/... to shadow bundled templates instead of editing vendor/. For anything else, prefer service decoration (#[AsDecorator]) over subclassing bundle internals — a decorator survives refactors better than an extends on a class the maintainer considers private.
Debugging checklist when an action returns the wrong thing: debug:router (is the right controller matched?) → debug:event-dispatcher kernel.view (is a converter swallowing the result?) → profiler's Request/Response panel (what actually got converted?) → var_dump in the converter. Nine times out of ten it's a listener with a broad type check matching your view object by accident.
Watch for deprecation noise. Small bundles often lag behind Symfony's current deprecation cycle — and this one is actively trimming old platform support, so constraint churn is to be expected. Run your suite with SYMFONY_DEPRECATIONS_HELPER=max[self]=0 after adding the bundle; if it floods the log, you've found the upgrade cost before it becomes a blocker on Symfony 8.
How can I help you explore Laravel packages today?