Skip to content

Let callbacks resolve against something other than the controller#17

Open
NielBuys wants to merge 2 commits into
developfrom
tasks/form_validation_feature
Open

Let callbacks resolve against something other than the controller#17
NielBuys wants to merge 2 commits into
developfrom
tasks/form_validation_feature

Conversation

@NielBuys

@NielBuys NielBuys commented Jul 26, 2026

Copy link
Copy Markdown
Owner

Two independent changes, both additive.

1. Separate the callback target from the $CI reference

CI_Form_validation::$CI was doing two unrelated jobs:

  • the handle for lang, uri, router and input
  • the only place a callback_ rule could be looked up

So code that wanted callbacks on anything but the controller — a model, a
service — had to overwrite $CI outright. That also handed away the core
services, and made the callback target depend on whoever assigned last.

The library is shared for the whole request and the assignment usually happens
in a constructor, so loading two such objects left the first one's callbacks
resolving to Unable to find callback validation rule. The rule never ran and
the field failed with:

Unable to access an error message corresponding to your field name X.

which reads like a missing language line, not a missing callback. That is a
long way from the cause, and it is why this took a while to find.

What changed

A separate $_callback_object, consulted only where callbacks are resolved:

$this->form_validation->set_callback_object($model)->run();
$this->form_validation->callback_object();   // read it back
$this->form_validation->set_callback_object(NULL);  // back to the CI instance

$CI is left alone, so the services keep working.

Backward compatibility

$_callback_object defaults to NULL, which means the CodeIgniter instance —
the historic behaviour. No existing caller has to change and none behaves
differently.
The only touched call site is the method_exists() / invoke
pair inside _execute(), which now asks callback_object() instead of reading
$this->CI directly.

Because the library is request-scoped, the docblock tells callers to set it
immediately before run() rather than once in a constructor — otherwise
whoever constructed last owns every lookup that follows, which is the original
bug wearing a different hat.

Tests

Seven new tests in Form_validation_test, covering: the default is the CI
instance; a set object receives the callback; $CI is untouched by setting it;
NULL restores the default; a non-object is ignored; the setter is chainable;
and a genuinely missing callback still fails cleanly rather than erroring.

2. .gitattributes: check out with LF

Fifteen tests fail in a Windows clone with core.autocrlf=true and pass
everywhere else — thirteen in Form_helper_test, one in Html_helper_test,
one in Calendar_test.

They are not PHP-version failures, which is what they look like at first.
Each compares helper output against a literal multi-line string written in the
test file. The helpers build output from "\n" escapes so they always emit LF,
while the expected value picks up whatever the file on disk uses. CRLF
checkout, CRLF expectation, LF actual, failure.

.gitattributes only carried export-ignore rules, so nothing told git how to
treat line endings and core.autocrlf won by default. Now * text=auto eol=lf,
plus *.bat text eol=crlf and binary rules.

Repository content is unchanged. git add --renormalize touched no file but
.gitattributes itself, confirming the blobs were already LF and only the
checkout differed. This affects the working tree only, and PHP does not care
either way.

Verification

Full suite, PHP 8.5:

Tests Assertions
develop 364 1170
this branch 371 1179

All passing. The 54 PHPUnit deprecations and 33 skips are pre-existing on
develop and unchanged here.

NielBuys and others added 2 commits July 26, 2026 09:29
$CI was doing two unrelated jobs: it is the handle for lang, uri, router
and input, and it was also the only place a callback_ rule could be
looked up. Code wanting callbacks on anything other than the controller
- a model, a service - had to overwrite $CI outright, which handed away
the services too and made the callback target depend on whoever assigned
last. Because the library is shared for the whole request and the
assignment typically happens in a constructor, loading two such objects
left the first one's callbacks resolving to "unable to find callback
validation rule". The rule never ran, and the field failed with

    Unable to access an error message corresponding to your field name X.

which reads like a missing language line rather than a missing callback.

Adds a separate _callback_object with set_callback_object() to set it and
callback_object() to read it, consulted only where callbacks are
resolved. It defaults to NULL, meaning the CodeIgniter instance, so every
existing caller behaves exactly as before and nothing needs to change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fifteen tests fail in a Windows clone with core.autocrlf=true and pass
everywhere else: thirteen in Form_helper_test, one in Html_helper_test,
one in Calendar_test.

They are not PHP version failures. Each compares helper output against a
literal multi-line string written in the test file. The helpers build
their output from "\n" escapes, so they always emit LF; the expected
value picks up whatever line ending the file on disk happens to have. A
CRLF checkout therefore compares a CRLF expectation against LF output.

.gitattributes only carried export-ignore rules, so nothing told git how
to treat line endings and core.autocrlf won by default.

Repository content is unchanged - git add --renormalize touched no file
but this one, confirming the blobs were already LF and only the checkout
differed. Nothing downstream is affected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant