Skip to content

feat: add FormRequest for encapsulating validation and authorization - #10087

Merged
michalsn merged 15 commits into
codeigniter4:4.8from
michalsn:feat/form-request
Apr 20, 2026
Merged

feat: add FormRequest for encapsulating validation and authorization#10087
michalsn merged 15 commits into
codeigniter4:4.8from
michalsn:feat/form-request

Conversation

@michalsn

Copy link
Copy Markdown
Member

Description
This PR introduces FormRequest, an abstract base class that lets you move validation rules, custom error messages, and authorization logic out of controller methods and into a dedicated class. You type-hint the class in the controller signature, and the framework resolves, authorizes, and validates the request automatically before the method body runs.

A make:request spark command scaffolds new classes. Injection works in both controller methods and closure routes, with route parameters resolved positionally alongside the FormRequest.

I'd like your opinions on whether this belongs in the framework. Personally, I find it useful - once an app grows, controllers tend to fill up with validation boilerplate. FormRequest moves that out, keeps controllers focused on the "happy path", and makes the rules reusable across endpoints.

Checklist:

  • Securely signed commits
  • Component(s) with PHPDoc blocks, only if necessary or adds value (without duplication)
  • Unit testing, with >80% coverage
  • User guide updated
  • Conforms to style guide

@michalsnmichalsn added new feature PRs for new features 4.8 PRs that target the `4.8` branch. labels Apr 6, 2026

@paulbalandanpaulbalandan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find the same named class in Laravel useful before for focused request validation. If the functionality is similar to that, then I think it would be also beneficial here.

Some initial thoughts:

Comment threadsystem/Commands/Generators/Views/formrequest.tpl.php Outdated
Comment threadsystem/HTTP/FormRequest.php Outdated
Comment threadsystem/HTTP/FormRequest.php Outdated
Comment threadsystem/Router/RouteCollectionInterface.php
@patel-vansh

Copy link
Copy Markdown
Contributor

Overall, this is a really nice feature to have. Most of the times, I have to create a final class which has static functions which return array of validation rules and call Validation service in controller. But looks like I have easier way to do that from 4.8.0.

Thanks @michalsn

@neznaika0

Copy link
Copy Markdown
Contributor

You've started developing magic (autowire). It would be great not to limit yourself to one form and continue the idea. set the Autowire interface and any available class can be connected as in other frameworks.

I think it can be done a long time ago. But the framework adheres to the rules from the 2000s =) Less obscure magic. Thus, we come to the need for DI/ServiceLocator, which is now replacing Services.

You can view https://github.com/PHP-DI/PHP-DI as a separate package.

@neznaika0

Copy link
Copy Markdown
Contributor

I'm already making a similar object for validation myself, only directly PostService::fromRequest(PostShema::rules())

@michalsn

Copy link
Copy Markdown
MemberAuthor

@paulbalandan I took the idea for this directly from Laravel.

@michalsn

Copy link
Copy Markdown
MemberAuthor

@neznaika0 Thanks for the feedback. FormRequest injection is intentionally narrow - it resolves one specific type at one specific point, not a general autowiring mechanism. Extending it into full container autowiring would go against CodeIgniter core philosophy of keeping things explicit and easy to follow. One of CI4 strengths is that you can trace exactly what's happening without implicit magic - and a general DI container trades that away.

Full DI container support is a legitimate feature in frameworks that prioritize that approach, but I don't believe it's the direction CI4 is going. That's not a weakness - it's more a positioning decision.

@neznaika0

Copy link
Copy Markdown
Contributor

That's the philosophy I'm talking about.

I'm worried that subsequent similar improvements will use the same principle, but duplicate the logic. Right now, I can't say what would be useful to dynamically add to the controllers.. Everything is possible - Models, Services, Configs.. in the end, we come to DI =).

I think that's why many people don't look towards CI, because they're used to magic SF, Laravel. Wordpress is an exception - it has a large community and allows non-standard approaches.

@michalsn

Copy link
Copy Markdown
MemberAuthor

I'm worried that subsequent similar improvements will use the same principle, but duplicate the logic. Right now, I can't say what would be useful to dynamically add to the controllers.. Everything is possible - Models, Services, Configs.. in the end, we come to DI =).

That's a real concern. FormRequest is a one-off - it solves a specific problem with a clear boundary. I don't plan to allow similar injections without a broader design discussion first.

In fact Services are CodeIgniter answer to dependency management - explicit, overridable, and testable. They're not a DI container in the classical sense, but they serve the same purpose without the implicit magic.

Comment threadsystem/HTTP/FormRequest.php Outdated
Comment threadsystem/HTTP/FormRequest.php Outdated
Comment threadsystem/HTTP/FormRequest.php Outdated
Comment threadtests/system/HTTP/FormRequestTest.php
@michalsn
michalsnforce-pushed the feat/form-request branch 2 times, most recently from ac35c4a to 342e1dfCompareApril 13, 2026 19:12
Comment threaduser_guide_src/source/incoming/form_requests/012.php
Comment threadsystem/Commands/Generators/Views/formrequest.tpl.php
Comment threadsystem/CodeIgniter.php
Comment threadsystem/CodeIgniter.php Outdated

@paulbalandanpaulbalandan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, LGTM!

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new CodeIgniter\HTTP\FormRequest abstraction to encapsulate validation rules, custom messages, and authorization in dedicated request classes, and adds framework-level injection so these requests are authorized/validated before controller/closure execution.

Changes:

  • Added FormRequest base class plus a FormRequestException short-circuit to return validation/authorization failure responses before the handler runs.
  • Updated dispatcher/controller invocation to resolve callable parameters via reflection (including FormRequest injection) and adjusted AutoRouterImproved’s URI-parameter counting.
  • Added make:request Spark generator scaffolding, tests, and user guide documentation/changelog entries.

Reviewed changes

Copilot reviewed 42 out of 42 changed files in this pull request and generated 6 comments.

Show a summary per file
FileDescription
utils/phpstan-baseline/missingType.callable.neonUpdated PHPStan baseline for newly introduced Closure-signature issues.
utils/phpstan-baseline/loader.neonUpdated PHPStan baseline totals.
utils/phpstan-baseline/argument.type.neonUpdated PHPStan baseline totals and removed now-unneeded ignores.
user_guide_src/source/libraries/validation.rstLinked validation docs to the new Form Requests documentation.
user_guide_src/source/incoming/index.rstAdded Form Requests to Incoming Request docs index.
user_guide_src/source/incoming/form_requests.rstAdded full user guide page documenting Form Requests.
user_guide_src/source/incoming/form_requests/001.phpUser guide example: basic FormRequest rules().
user_guide_src/source/incoming/form_requests/002.phpUser guide example: controller injection and validated().
user_guide_src/source/incoming/form_requests/003.phpUser guide example: route params + FormRequest ordering.
user_guide_src/source/incoming/form_requests/004.phpUser guide example: custom messages().
user_guide_src/source/incoming/form_requests/005.phpUser guide example: isAuthorized().
user_guide_src/source/incoming/form_requests/006.phpUser guide example: prepareForValidation().
user_guide_src/source/incoming/form_requests/007.phpUser guide example: overriding validationData().
user_guide_src/source/incoming/form_requests/008.phpUser guide example: overriding failure responses.
user_guide_src/source/incoming/form_requests/009.phpUser guide example: validated() output.
user_guide_src/source/incoming/form_requests/010.phpUser guide example: accessing underlying IncomingRequest.
user_guide_src/source/incoming/form_requests/011.phpUser guide example: closure-route injection.
user_guide_src/source/incoming/form_requests/012.phpUser guide example: manual resolveRequest() in _remap().
user_guide_src/source/incoming/form_requests/013.phpUser guide example: flashing normalized values after failure.
user_guide_src/source/incoming/form_requests/014.phpUser guide example: getValidated()/hasValidated() with dot syntax.
user_guide_src/source/changelogs/v4.8.0.rstChangelog entries for FormRequest and make:request.
tests/system/Router/Controllers/Requests/MyFormRequest.phpTest fixture FormRequest for AutoRouterImproved routing tests.
tests/system/Router/Controllers/Mycontroller.phpAdded controller methods that include FormRequest parameters.
tests/system/Router/AutoRouterImprovedTest.phpTests ensuring FormRequest params don’t consume URI segments.
tests/system/HTTP/FormRequestTest.phpComprehensive unit/integration coverage for FormRequest behavior.
tests/system/Commands/Utilities/Routes/ControllerFinderTest.phpUpdated expected controller discovery to include new test controller.
tests/system/Commands/Utilities/Routes/AutoRouteCollectorTest.phpUpdated expected autoroute collection output.
tests/system/Commands/Generators/FormRequestGeneratorTest.phpAdded generator tests for make:request scaffolding.
tests/_support/HTTP/Requests/ValidPostFormRequest.phpSupport FormRequest for tests requiring valid rules.
tests/_support/HTTP/Requests/UnauthorizedFormRequest.phpSupport FormRequest for authorization-failure tests.
tests/_support/Controllers/FormRequestController.phpSupport controller for FormRequest injection integration tests.
system/Router/RouterInterface.phpAdjusted PHPDoc return types related to closures/controller resolution.
system/Router/Router.phpAdjusted PHPDoc types for $controller and controllerName() docs.
system/Router/RouteCollectionInterface.phpAdjusted PHPDoc for route handler parameter types.
system/Router/RouteCollection.phpAdjusted PHPDoc for route handler parameter types.
system/Router/AutoRouterImproved.phpExcluded FormRequest params from URI segment count check.
system/Language/en/CLI.phpAdded CLI language key for request generator prompt.
system/HTTP/FormRequest.phpNew FormRequest base class (rules/messages/auth/validation/validated accessors).
system/HTTP/Exceptions/FormRequestException.phpNew ResponsableInterface exception used to short-circuit responses.
system/Commands/Generators/Views/formrequest.tpl.phpNew generator template for FormRequest scaffolding.
system/Commands/Generators/FormRequestGenerator.phpNew make:request generator command implementation.
system/CodeIgniter.phpAdded callable parameter resolution and FormRequest injection for controllers/closures.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadsystem/Router/AutoRouterImproved.php Outdated
Comment threadsystem/Router/RouteCollectionInterface.php
Comment threadsystem/Router/RouterInterface.php
Comment threadutils/phpstan-baseline/missingType.callable.neon
Comment threadtests/system/HTTP/FormRequestTest.php Outdated
Comment threadsystem/CodeIgniter.php

CopilotAI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 44 out of 44 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment threadsystem/CodeIgniter.php
Comment threadsystem/HTTP/Exceptions/FormRequestException.php
Comment threadsystem/HTTP/FormRequest.php
Comment threadsystem/CodeIgniter.php
Comment threadsystem/Router/RouterInterface.php
@michalsn

Copy link
Copy Markdown
MemberAuthor

@paulbalandan When you have time, could you take one last look at the code? I fixed one bug and did a small refactor after Copilot review. Thanks!

@paulbalandanpaulbalandan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some minor comments.

Comment threadsystem/HTTP/Exceptions/FormRequestException.php
Comment threadsystem/HTTP/FormRequest.php Outdated

@datamwebdatamweb left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey team,
After months of no access to international internet, I’m back.
This is a feature I realized a few months ago that we were missing in the codeigniter4, and I’m glad you’ve implemented it.
LGTM.

@michalsn
michalsn merged commit 80f8cd8 into codeigniter4:4.8Apr 20, 2026
60 of 61 checks passed
@michalsn

Copy link
Copy Markdown
MemberAuthor

Thanks everyoune for the reviews!

@memleakdmemleakd mentioned this pull request May 4, 2026
5 tasks
@michalsn
michalsn deleted the feat/form-request branch August 10, 2026 05:32
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

4.8PRs that target the `4.8` branch.new featurePRs for new features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@michalsn@patel-vansh@neznaika0@datamweb@paulbalandan