ctw/ctw-cast

This package provides utility classes for type-safe, exception-free casting in PHP 8.3+.

Maintainers

Package info

github.com/jonathanmaron/ctw-cast

pkg:composer/ctw/ctw-cast

Statistics

Installs: 17

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

2.0.0 2026-04-18 05:53 UTC

This package is auto-updated.

Last update: 2026-04-18 05:53:37 UTC


README

Latest Stable Version GitHub Actions Scrutinizer Build Scrutinizer Quality Code Coverage

Type-safe, exception-free casting utility for PHP 8.3+ applications.

Introduction

Why This Library Exists

Modern PHP development with strict types (declare(strict_types=1)) demands precise type handling. However, many data sources in PHP return mixed or loosely-typed values:

  • Superglobals ($_GET, $_POST, $_SERVER, $_ENV, $_COOKIE, $_SESSION) return mixed
  • Environment variables via getenv() return string|false
  • Legacy libraries often return untyped or loosely-typed data
  • Database results may return strings for numeric columns
  • Configuration files (JSON, XML, YAML, INI) parse to mixed arrays
  • External APIs return decoded JSON with uncertain types

PHP's native type casting ((int), (string), etc.) is permissive and can silently produce unexpected results:

(int) "42abc";     // 42 (silently ignores "abc")
(int) [];          // 0 (arrays become 0 or 1)
(bool) "false";    // true (non-empty string)
(float) "invalid"; // 0.0 (no error)

This library provides predictable, exception-free type conversions. When a value cannot be cast, a safe default is returned instead of throwing an exception, making it ideal for use in hot paths, middleware, and defensive code where unexpected input must never interrupt execution.

Default Return Values

When a value cannot be cast to the target type, the following defaults are returned:

Method Default Return
toArray []
toBool false
toFloat 0.0
toInt 0
toJson '{}'
toString ''

Problems This Library Solves

  1. Silent data corruption: Native casts convert invalid values in unpredictable ways; this library uses clear, documented rules
  2. Inconsistent boolean handling: PHP treats "false", "no", "off" as true; this library treats them as false
  3. Missing validation: No built-in way to reject non-numeric strings for number conversion
  4. Overflow handling: Native (int) silently wraps on overflow; this library returns 0
  5. Type ambiguity: Superglobals and legacy code return mixed, breaking strict typing

Accessing Data from Superglobals

When working with $_GET, $_POST, $_SERVER, $_ENV, and other superglobals, values are always mixed. This library ensures type-safe access:

use Ctw\Cast\Cast;

// Environment variables
$debug    = Cast::toBool($_ENV['DEBUG'] ?? 'false');
$port     = Cast::toInt($_ENV['DB_PORT'] ?? '3306');
$timeout  = Cast::toFloat($_ENV['TIMEOUT'] ?? '30.0');
$appName  = Cast::toString($_ENV['APP_NAME'] ?? '');

// GET/POST parameters
$page     = Cast::toInt($_GET['page'] ?? '1');
$limit    = Cast::toInt($_GET['limit'] ?? '10');
$active   = Cast::toBool($_POST['active'] ?? 'false');
$tags     = Cast::toArray($_POST['tags'] ?? '[]');

// Server variables
$port     = Cast::toInt($_SERVER['SERVER_PORT'] ?? '80');
$https    = Cast::toBool($_SERVER['HTTPS'] ?? 'off');

// Session data
$userId   = Cast::toInt($_SESSION['user_id'] ?? '0');
$prefs    = Cast::toArray($_SESSION['preferences'] ?? '{}');

Working with Legacy Libraries

Many older PHP libraries return untyped data. This library bridges the gap between legacy code and modern strict-typed applications:

use Ctw\Cast\Cast;

// Legacy database result (returns strings for all columns)
$row    = $legacyDb->fetchRow("SELECT id, price, active FROM products");
$id     = Cast::toInt($row['id']);
$price  = Cast::toFloat($row['price']);
$active = Cast::toBool($row['active']);

// Legacy configuration loader
$config     = $legacyLoader->load('app.ini');
$maxSize    = Cast::toInt($config['upload_max_size']);
$debugMode  = Cast::toBool($config['debug']);
$allowedIps = Cast::toArray($config['allowed_ips']);

// Legacy API client
$response = $legacyClient->get('/api/users');
$users    = Cast::toArray($response);
$jsonOut  = Cast::toJson($users);

// Untyped function returns
$result = some_legacy_function();
$count  = Cast::toInt($result);

Where to Use This Library

  • Controllers: Cast request parameters to expected types
  • Service layers: Ensure type safety when processing external data
  • CLI commands: Parse command-line arguments and environment variables
  • Data mappers: Convert database results to typed domain objects
  • API handlers: Decode incoming JSON payloads
  • Configuration loaders: Parse config values
  • Queue workers: Process messages with mixed payloads
  • Middleware: Normalize request/response data

Design Goals

  1. Never throw: Every method returns a safe default when a value cannot be cast
  2. Explicit behavior: Every conversion rule is documented and predictable
  3. No silent corruption: Ambiguous values produce the documented default, not a guess
  4. PHPStan/Psalm friendly: Return types are precise, enabling static analysis
  5. Zero dependencies: No external packages required

Requirements

  • PHP 8.3 or higher
  • strict_types enabled

Installation

Install by adding the package as a Composer requirement:

composer require ctw/ctw-cast

Usage Examples

use Ctw\Cast\Cast;

$string = Cast::toString($value);
$int    = Cast::toInt($value);
$float  = Cast::toFloat($value);
$bool   = Cast::toBool($value);
$array  = Cast::toArray($value);
$json   = Cast::toJson($value);

Cast::toString(mixed $value): string

Converts values to string representation with explicit, predictable rules. Returns '' for values that cannot be cast.

Cast::toString(42);           // "42"
Cast::toString(true);         // "1"
Cast::toString(null);         // ""
Cast::toString([1, 2, 3]);    // ""
Input Type Input Value Output
string "hello" "hello"
int 42 "42"
int -17 "-17"
int 0 "0"
float 3.14 "3.14"
float -2.5 "-2.5"
float 1.0 "1"
float INF "INF"
float NAN "NAN"
bool true "1"
bool false "0"
null null ""
object with __toString() __toString() result
object without __toString() ""
array [1, 2, 3] ""
resource fopen(...) ""

Cast::toInt(mixed $value): int

Converts values to integers with rounding for floats. Returns 0 for values that cannot be cast.

Cast::toInt("42");        // 42
Cast::toInt(3.7);         // 4 (rounded)
Cast::toInt(null);        // 0
Cast::toInt("hello");     // 0
Cast::toInt(INF);         // 0
Input Type Input Value Output
int 42 42
int -17 -17
bool true 1
bool false 0
null null 0
float 3.14 3 (rounded)
float 3.5 4 (rounded)
float -2.7 -3 (rounded)
string "42" 42
string " 42 " 42 (trimmed)
string "3.14" 3 (rounded)
string "1e3" 1000
float INF 0
float NAN 0
float 1e20 (overflow) 0
string "" 0
string "hello" 0
string "42abc" 0
string out of int range 0
array [1, 2, 3] 0
object stdClass 0
resource fopen(...) 0

Cast::toFloat(mixed $value): float

Converts values to floating-point numbers. Returns 0.0 for values that cannot be cast.

Cast::toFloat("3.14");    // 3.14
Cast::toFloat(42);        // 42.0
Cast::toFloat(true);      // 1.0
Cast::toFloat("hello");   // 0.0
Input Type Input Value Output
float 3.14 3.14
float -2.5 -2.5
float INF INF
float NAN NAN
int 42 42.0
int -17 -17.0
int 0 0.0
bool true 1.0
bool false 0.0
null null 0.0
string "3.14" 3.14
string " 3.14 " 3.14 (trimmed)
string "42" 42.0
string "1e3" 1000.0
string "-2.5" -2.5
string "" 0.0
string "hello" 0.0
string "42abc" 0.0
array [1, 2, 3] 0.0
object stdClass 0.0
resource fopen(...) 0.0

Cast::toBool(mixed $value): bool

Strict boolean conversion with explicit allowed values. Returns false for values that cannot be interpreted unambiguously.

Cast::toBool("yes");   // true
Cast::toBool(0);       // false
Cast::toBool(null);    // false
Cast::toBool("maybe"); // false
Cast::toBool(42);      // false
Input Type Input Value Output
bool true true
bool false false
int 1 true
int 0 false
float 1.0 true
float 0.0 false
null null false
string "true" true
string "1" true
string "yes" true
string "on" true
string "y" true
string "t" true
string "false" false
string "0" false
string "no" false
string "off" false
string "n" false
string "f" false
string "" false
string " TRUE " true (trimmed, case-insensitive)
int 2 false
int -1 false
float 3.14 false
float -1.0 false
string "hello" false
string "2" false
array [1, 2, 3] false
object stdClass false
resource fopen(...) false

Cast::toArray(mixed $value): array

Intelligent array conversion with multiple strategies for different types. Returns [] for values that cannot be cast.

Cast::toArray('{"a":1}');   // ["a" => 1]
Cast::toArray(42);          // [42]
Cast::toArray(null);        // []
Input Type Input Value Output
array [1, 2, 3] [1, 2, 3]
null null []
string "" []
string " " [] (trimmed)
string '{"a":1}' ["a" => 1] (JSON parsed)
string '[1,2,3]' [1, 2, 3] (JSON parsed)
string '{invalid}' ['{invalid}'] (wrapped)
string "hello" ["hello"] (wrapped)
int 42 [42]
float 3.14 [3.14]
bool true [true]
object Traversable iterator_to_array() result
object with toArray() toArray() result
object stdClass{a:1} ["a" => 1] (via get_object_vars)
resource fopen(...) []

Cast::toJson(mixed $value, int $flags = ..., int $depth = 512): string

Type-safe JSON encoding. Returns '{}' when encoding fails (INF/NAN, invalid UTF-8, depth exceeded, resources, etc.).

Cast::toJson(['name' => 'John']);   // '{"name":"John"}'
Cast::toJson(true);                 // 'true'
Cast::toJson(null);                 // 'null'
Cast::toJson(INF);                  // '{}'
Input Type Input Value Output
null null "null"
string "hello" "\"hello\""
string "with/slash" "\"with/slash\""
int 42 "42"
int -17 "-17"
bool true "true"
bool false "false"
float 3.14 "3.14"
array [1, 2, 3] "[1,2,3]"
array ["a" => 1] "{\"a\":1}"
object JsonSerializable via jsonSerialize()
object with toArray() via toArray()
object stdClass{a:1} "{\"a\":1}"
float INF "{}"
float NAN "{}"
(any) depth < 1 "{}"
object toArray() not returning array "{}"
resource fopen(...) "{}"

Default flags: JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE

Error Handling

This library never throws exceptions. When a value cannot be cast to the target type, a documented default value is returned instead:

use Ctw\Cast\Cast;

$int   = Cast::toInt($userInput);   // 0 if not castable
$str   = Cast::toString($userInput); // '' if not castable
$bool  = Cast::toBool($userInput);   // false if not castable
$float = Cast::toFloat($userInput);  // 0.0 if not castable
$arr   = Cast::toArray($userInput);  // [] if not castable
$json  = Cast::toJson($userInput);   // '{}' if not castable

Because no exceptions are thrown, there is no need for try/catch blocks around cast calls.