nowo-tech / controller-kit-bundle
Controller utilities for Symfony: redirectToReferer (configurable default route), SafeForwardTrait for safe request forwarding, RequiresValidFormTrait form guards and the #[MapScopedEntity] owner-scoped Doctrine entity argument resolver.
Package info
github.com/nowo-tech/ControllerKitBundle
Type:symfony-bundle
pkg:composer/nowo-tech/controller-kit-bundle
Requires
- php: >=8.2 <8.6
- symfony/config: ^7.0 || ^8.0
- symfony/dependency-injection: ^7.0 || ^8.0
- symfony/http-foundation: ^7.0 || ^8.0
- symfony/http-kernel: ^7.0 || ^8.0
- symfony/routing: ^7.0 || ^8.0
Requires (Dev)
- doctrine/doctrine-bundle: ^2.13 || ^3.0
- doctrine/orm: ^3.4
- friendsofphp/php-cs-fixer: ^3.0
- igor-php/igor-php: ^0.10.1
- nowo-tech/phpstan-frankenphp: ^1.0
- phpstan/extension-installer: ^1.4
- phpstan/phpstan: ^2.0
- phpstan/phpstan-symfony: ^2.0
- phpunit/phpunit: ^10.0
- rector/rector: ^2.0
- symfony/form: ^7.0 || ^8.0
- symfony/framework-bundle: ^7.0 || ^8.0
- symfony/phpunit-bridge: ^7.0 || ^8.0
- symfony/security-core: ^7.0 || ^8.0
- symfony/security-csrf: ^7.0 || ^8.0
- symfony/yaml: ^7.0 || ^8.0
Suggests
- doctrine/doctrine-bundle: Required (with doctrine/orm) to use the #[MapScopedEntity] argument resolver; it provides the `doctrine` registry
- doctrine/orm: Required (^2.17 || ^3.0, with doctrine/doctrine-bundle) to use the #[MapScopedEntity] owner-scoped entity argument resolver
- symfony/flex: For automatic recipe installation
- symfony/form: Required when using RequiresValidFormTrait (form guard helpers)
- symfony/framework-bundle: Required in your application when using the traits with AbstractController
- symfony/security-core: Required when using RequiresValidFormTrait (AbstractController::createAccessDeniedException)
Provides
None
Conflicts
None
Replaces
None
README
⭐ Found this useful? Install from Packagist · Give it a star on GitHub so more developers can find it.
Controller Kit Bundle — Utilities for Symfony controllers: redirectToReferer (configurable default route) and SafeForwardTrait for safe request forwarding, RequiresValidFormTrait form guards, and the #[MapScopedEntity] owner-scoped Doctrine entity argument resolver. Tested on Symfony 7.4, 8.0, and 8.1 (also compatible with Symfony 7.0–7.3) · PHP 8.2+ (Symfony 8.x requires PHP 8.4+).
This bundle is FrankenPHP worker mode friendly.
Workers that do not reset the kernel between requests are supported too (no mutable package state). See docs/FRANKENPHP-WORKER-AUDIT.md.
Features
- RedirectToRefererTrait — Redirect to the HTTP Referer when valid (same app, route exists), or to a configurable default route. 100% configurable via
nowo_controller_kit.default_route. - SafeForwardTrait — Forward to another controller method with a check that the method exists (avoids runtime errors).
- RequiresValidFormTrait — Form guards for
AbstractController:requireValidForm()/requireValidCsrfForm()deny with 403 when a form is not submitted or invalid;acceptSubmittedForm()turns an invalid user-facing field into an error flash instead of a 403 (CSRF failures still 403). Needssymfony/form(suggested). #[MapScopedEntity]— Controller argument attribute that loads a Doctrine entity with one query joined on its owner (another route attribute): another owner's entity is the same 404 as a missing one (no existence leak, no N+1, no manualgetOwner() !== $ownerchecks). Nullable arguments getnull. Needsdoctrine/orm+doctrine/doctrine-bundle(suggested; the resolver is only registered when they are installed).
Installation
composer require nowo-tech/controller-kit-bundle
With Symfony Flex, the recipe registers the bundle and adds config. Without Flex, see docs/INSTALLATION.md.
Manual registration in config/bundles.php:
return [ // ... Nowo\ControllerKitBundle\NowoControllerKitBundle::class => ['all' => true], ];
Requirements
- PHP
>=8.2(<8.6); Symfony 8.0 and 8.1 require PHP 8.4+ - Symfony 7.4, 8.0, or 8.1 (minimum supported minors; also works on Symfony 7.0–7.3 via
composer.jsonconstraints) - For traits:
symfony/framework-bundlein your application (AbstractController) - For
RequiresValidFormTrait:symfony/formandsymfony/security-core - For
#[MapScopedEntity]:doctrine/orm(^2.17 or ^3) anddoctrine/doctrine-bundle
Configuration
In config/packages/nowo_controller_kit.yaml:
nowo_controller_kit: default_route: homepage # Route used when redirectToReferer has no valid Referer
Use your own route name (e.g. app_home, dashboard).
Usage
redirectToReferer
Use the trait in a controller that extends AbstractController:
use Nowo\ControllerKitBundle\Controller\RedirectToRefererTrait; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; class MyController extends AbstractController { use RedirectToRefererTrait; public function submit(Request $request): Response { // ... handle form ... return $this->redirectToReferer($request); // Optional: merge params and set status // return $this->redirectToReferer($request, ['success' => 1], 303); } }
When the request has a valid Referer header (same host as the current request, path matches a route in your app), the user is redirected there (with path and query params preserved). Otherwise they are redirected to the configured default_route.
safeForward
Use the trait in any controller that has forward() (e.g. AbstractController):
use Nowo\ControllerKitBundle\Controller\SafeForwardTrait; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; class ApiController extends AbstractController { use SafeForwardTrait; public function delegate(): Response { return $this->safeForward( OtherController::class, 'actionName', ['id' => 123], ['page' => 1] ); } }
If OtherController::actionName does not exist, a BadMethodCallException is thrown instead of a generic error.
requireValidForm / requireValidCsrfForm / acceptSubmittedForm
use Nowo\ControllerKitBundle\Controller\RequiresValidFormTrait; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; class MemberController extends AbstractController { use RequiresValidFormTrait; public function delete(Request $request): Response { $form = $this->createForm(DeleteMemberType::class)->handleRequest($request); $this->requireValidCsrfForm($form); // 403 "Invalid CSRF token." when not submitted / invalid // ... } public function changeRole(Request $request): Response { $form = $this->createForm(MemberRoleType::class)->handleRequest($request); if (!$this->acceptSubmittedForm($form, 'role', 'flash.member_invalid_role')) { return $this->redirectToRoute('members'); // only "role" was invalid: error flash already added } // ... } }
Requires symfony/form and symfony/security-core in your application (suggested, not required, by the bundle).
#[MapScopedEntity]
use Nowo\ControllerKitBundle\Attribute\MapScopedEntity; #[Route('/projects/{projectId}/issues/{issueId}')] public function show( #[MapScopedEntity(parent: 'project', parentField: 'uuid', id: 'issueId', field: 'uuid')] Issue $issue, // 404 when missing OR when it belongs to another project ): Response { // ... }
parent is the owner association on the entity, parentId the route attribute with the owner identifier (default {parent}Id), parentField the owner field it matches (default id); id / field do the same for the entity (default id / id); objectManager picks a named entity manager. See docs/USAGE.md.
Demo
demo/symfony8— Symfony 8.1 (FrankenPHP PHP 8.5), host port 8011 by default
The demo runs FrankenPHP + Caddy in Docker with APP_ENV=dev. Runtime mode is FRANKENPHP_MODE (worker default, or classic for per-request PHP) — see docs/DEMO-FRANKENPHP.md.
Global demo commands: make -C demo help (e.g. make -C demo up-symfony8).
Development
make up
make install
make test
make cs-check
make phpstan
make igor
make release-check
Documentation
- Installation
- Configuration
- Usage
- Contributing
- Code of Conduct
- Changelog
- Upgrading
- Release
- Security
- Engram
- Spec-driven development
- GitHub Spec Kit
Additional documentation
- PSR evaluation (REQ-CS-007)
- Demo (FrankenPHP)
- FrankenPHP worker audit (kernel not reset)
- GitHub Actions CI requirements
Tests and coverage
- Tests: PHPUnit (PHP)
- PHP: 100%
License and author
MIT · Nowo.tech · Héctor Franco Aceituno
