ADR 017 · Accepted

Safe Type Casting via SafeCastTrait

Context

Processing untyped data from JSON API responses, form submissions, and configuration arrays requires casting mixed values to specific scalar types. At PHPStan level 10, direct casts like (string)$mixed trigger "Cannot cast mixed to string" errors. Each usage site would need inline type guards, leading to repetitive boilerplate.

Problem statement

  1. PHPStan level 10 strictness: (string)$data['key'] is forbidden on mixed.

  2. Verbose alternatives: is_string($v) ? $v : (is_numeric($v) ? (string)$v : '') at every call site.

  3. Inconsistent defaults: Different code paths used different fallback values.

  4. Suppression temptation: Teams resort to @phpstan-ignore instead of proper narrowing.

Decision

Extract a reusable SafeCastTrait with three static methods that handle mixed input with sensible defaults and no PHPStan suppressions:

Design choices:

  • Static methods -- No instance state needed; enables self::toStr() calls.

  • Private visibility -- Implementation detail of the using class, not public API.

  • Numeric passthrough -- is_numeric() covers int, float, and numeric strings.

  • Empty-string default -- Safer than null for string contexts (concatenation, comparison).

  • Zero default for int/float -- Neutral value for arithmetic operations.

Complements the ResponseParserTrait in Classes/Provider/ which serves a similar purpose for provider API response arrays but with key-based access (getString($data, 'key')). SafeCastTrait handles standalone values.

Usage in WizardGeneratorService:

Consequences

Positive:

  • ●● PHPStan level 10 compliance without any @phpstan-ignore suppressions.

  • ● Consistent fallback behavior across all consumers.

  • ● Three-line methods are trivially testable and auditable.

  • ◐ Reduces boilerplate by ~5 lines per cast site.

Negative:

  • ◑ Trait usage adds an indirect dependency (mitigated by being a small utility).

  • is_numeric() accepts numeric strings like "1e2" which may surprise.

Net Score: +4.5 (Positive)

Files changed

Added:

  • Classes/Utility/SafeCastTrait.php

Modified (consumers):

  • Classes/Service/WizardGeneratorService.php -- Uses SafeCastTrait for JSON normalization.

  • Classes/Controller/Backend/TaskWizardController.php -- Uses SafeCastTrait for form data casting (the monolithic TaskController was split per ADR-027).

← All ADRs