Skip to content

perf: cache compiled type cast rules in DataCaster - #10708

Open
gr8man wants to merge 1 commit into
codeigniter4:developfrom
gr8man:perf/datacaster-type-cache
Open

gr8man wants to merge 1 commit into
codeigniter4:developfrom
gr8man:perf/datacaster-type-cache

Conversation

@gr8man

@gr8man gr8man commented Nov 1, 2026

Copy link
Copy Markdown
Contributor

Description

This PR optimizes DataCaster::castAs() by compiling and caching type cast rules on demand per unique type in DataCaster::$compiledTypes.

Motivation and Context

In DataCaster::castAs(), hydrating model rows and entity attributes repeatedly triggered the following operations for every single field of every hydrated row:

  1. preg_match('/\A(.+)\[(.+)\]\z/', $type, $matches) to detect array/parameterized cast definitions (e.g., json[array], datetime[Y-m-d]).
  2. explode(',', ...) and array_map(trim(...), ...) to parse parameters.
  3. Multiple reflection checks via is_subclass_of($handler, CastInterface::class) and is_subclass_of($handler, EntityCastInterface::class) when $strict === false (the default mode used by Entity casting).

For a query fetching 1,000 rows with 10 fields per row, this resulted in 10,000 regex executions and 20,000 reflection checks.

By caching the compiled [$handler, $params] keyed by $type:

  • All fields sharing the same cast type (e.g. int, bool, csv) share the compiled rule across the lifetime of the caster.
  • preg_match, explode, trim, and is_subclass_of execute only once per unique cast type.
  • $type = $this->types[$field] ?? null provides a single array lookup fast path.
  • setTypes(array $types) preserves the compiled cache when called with identical types (as Entity::castAs() does on every field), while safely invalidating $compiledTypes if types are modified dynamically.

Benchmark & Performance Impact

Microbenchmark executing 900,000 castAs() calls with a mix of types (int, bool, float, csv, json[array], ?int):

  • Before: ~6,440 ms (7.16 us / call) - ~139,000 casts/s
  • After: ~2,943 ms (3.27 us / call) - >305,000 casts/s
  • Speedup: 2.18x faster (-54% time)

Backward Compatibility (100% BC)

  • DataCaster is a final class with private properties; no public or protected API signatures have changed.
  • The order and semantics of null handling and exceptions are strictly preserved:
    • Null value for nullable field (?type) immediately returns null.
    • Null value for non-nullable field in strict mode throws InvalidArgumentException('Field "..." is not nullable, but null was passed.') before checking handler validity.
    • Invalid method throws CastException::forInvalidMethod() upfront.

Checklist

  • Code follows the style guidelines of this project
  • Tests have been added to cover the changes
  • Static analysis (PHPStan) passes without errors
  • PHP-CS-Fixer passes without errors

@carson-codeigniter4 carson-codeigniter4 Bot added needs template Opened issues not following the bug form template enhancement PRs that improve existing functionalities labels Nov 1, 2026
@carson-codeigniter4

Copy link
Copy Markdown

Hi there, @gr8man! 👋

It looks like this pull request does not follow our template:

Please update the description to follow the template. The needs template label will be removed automatically once it does.

Caches parsed cast rules (handler class, extracted parameters) on demand per unique type
in DataCaster. This eliminates repeated regex pattern matching (preg_match), string
manipulation (explode, trim), and reflection interface checks (is_subclass_of)
for every field on every row hydrated during Entity and Model operations.

Benchmark on 900,000 castAs() calls:
- Before: ~6,440 ms (7.16 us/call)
- After:  ~2,943 ms (3.27 us/call) - 2.18x speedup (-54% time, >305k casts/s)
@gr8man
gr8man force-pushed the perf/datacaster-type-cache branch from 4ab52ef to 7777bf0 Compare November 1, 2026 23:15

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement PRs that improve existing functionalities needs template Opened issues not following the bug form template

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant