ctw / ctw-cast
This package provides utility classes for type-safe, exception-free casting in PHP 8.3+.
Requires
- php: ^8.3
Requires (Dev)
- ctw/ctw-qa: ^5.0
- phpunit/phpunit: ^12.0
- symfony/var-dumper: ^7.0
This package is auto-updated.
Last update: 2026-04-18 05:53:37 UTC
README
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) returnmixed - Environment variables via
getenv()returnstring|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
- Silent data corruption: Native casts convert invalid values in unpredictable ways; this library uses clear, documented rules
- Inconsistent boolean handling: PHP treats
"false","no","off"astrue; this library treats them asfalse - Missing validation: No built-in way to reject non-numeric strings for number conversion
- Overflow handling: Native
(int)silently wraps on overflow; this library returns0 - 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
- Never throw: Every method returns a safe default when a value cannot be cast
- Explicit behavior: Every conversion rule is documented and predictable
- No silent corruption: Ambiguous values produce the documented default, not a guess
- PHPStan/Psalm friendly: Return types are precise, enabling static analysis
- 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.