承接 joby/smol-cast 相关项目开发

从需求分析到上线部署,全程专人跟进,保证项目质量与交付效率

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

joby/smol-cast

Composer 安装命令:

composer require joby/smol-cast

包简介

Predictable type coercion from mixed inputs with explicit error handling.

README 文档

README

Predictable type coercion from mixed input with explicit error handling.

Installation

composer require joby-lol/smol-cast

About

smolCast provides type-safe conversion methods for scalar values with permissive coercion rules and clear error handling. Designed for parsing user input, query strings, and decoded data where types may not match expectations.

Key features:

  • Permissive coercion: Accepts multiple input types for each target type
  • Explicit errors: Throws exceptions instead of silent failures
  • Null-safe: Returns null for null input, throws for unconvertible values
  • No dependencies: Pure PHP with no external requirements

Basic Usage

use Joby\Smol\Cast\Cast;

// Returns null for null input
Cast::int(null);    // null
Cast::float(null);  // null
Cast::bool(null);   // null
Cast::string(null); // null

// Converts compatible types
Cast::int("42");     // 42
Cast::float("3.14"); // 3.14
Cast::bool("yes");   // true
Cast::string(123);   // "123"

// Throws TypeCastException for incompatible types
Cast::int("not a number"); // throws
Cast::bool("maybe");       // throws

Type Conversion Rules

int()

Converts to integer with the following rules:

  • int: Returned as-is
  • bool: true → 1, false → 0
  • float: Converted if whole number within int range (123.0 → 123, 123.5 throws)
  • string/Stringable: Parsed if numeric (whitespace trimmed, "-42" and "3.0" work)
Cast::int(42);        // 42
Cast::int(true);      // 1
Cast::int(3.0);       // 3
Cast::int("  -42  "); // -42
Cast::int(3.14);      // throws (fractional part)
Cast::int("abc");     // throws (not numeric)

float()

Converts to float with the following rules:

  • float: Returned as-is
  • int: Converted to float
  • bool: true → 1.0, false → 0.0
  • string/Stringable: Parsed if numeric (whitespace trimmed)
Cast::float(3.14);      // 3.14
Cast::float(42);        // 42.0
Cast::float(true);      // 1.0
Cast::float("  2.5  "); // 2.5
Cast::float("abc");     // throws (not numeric)

bool()

Converts to boolean with the following rules:

  • bool: Returned as-is
  • int: 1 → true, 0 → false (other values throw)
  • float: 1.0 → true, 0.0 → false (other values throw)
  • string/Stringable: Parsed case-insensitively (whitespace trimmed):
    • True: "1", "true", "yes", "on"
    • False: "0", "false", "no", "off", "" (empty string)
Cast::bool(true);    // true
Cast::bool(1);       // true
Cast::bool("yes");   // true
Cast::bool("YES");   // true (case-insensitive)
Cast::bool(0);       // false
Cast::bool("");      // false (empty string)
Cast::bool("   ");   // false (whitespace-only)
Cast::bool("maybe"); // throws (invalid string)
Cast::bool(2);       // throws (invalid int)

string()

Converts to string with the following rules:

  • string: Returned as-is
  • bool: true → "1", false → "" (empty string)
  • int/float: Standard string conversion
  • Stringable: Calls __toString()
Cast::string("hello"); // "hello"
Cast::string(true);    // "1"
Cast::string(false);   // ""
Cast::string(42);      // "42"
Cast::string(3.14);    // "3.14"
Cast::string([1,2,3]); // throws (not scalar)

Usage Patterns

Query String Parsing

// $_GET['page'] might be string "5" or missing
$page = Cast::int($_GET['page'] ?? null) ?? 1;

// $_GET['debug'] might be "1", "true", "yes", etc.
$debug = Cast::bool($_GET['debug'] ?? null) ?? false;

Decoded JSON/API Data

$data = json_decode($response, true);

// Data types might not match expectations
$userId = Cast::int($data['user_id'] ?? null);
$isActive = Cast::bool($data['active'] ?? null);
$price = Cast::float($data['price'] ?? null);

Database Result Handling

// Some DB drivers return all values as strings
$row = $pdo->fetch(PDO::FETCH_ASSOC);

$id = Cast::int($row['id']);            // "42" → 42
$amount = Cast::float($row['amount']);  // "19.99" → 19.99
$enabled = Cast::bool($row['enabled']); // "1" → true

Form Input Validation

try {
    $age = Cast::int($_POST['age']);
    $email = Cast::string($_POST['email']);
    $subscribe = Cast::bool($_POST['subscribe'] ?? null) ?? false;
    
    // Process validated data
} catch (TypeCastException $e) {
    // Handle invalid input
}

Exceptions

All methods throw TypeCastException (extends Exception) when a value cannot be converted to the target type. Null input always returns null and never throws.

use Joby\Smol\Cast\TypeCastException;

try {
    $value = Cast::int($_GET['count']);
} catch (TypeCastException $e) {
    // Handle conversion error
    $value = 1; // fallback
}

CastingGettersTrait

The CastingGettersTrait provides a convenient way to add type-safe getters to any class that stores keyed values. Perfect for configuration objects, request wrappers, or any class that needs to convert raw data to specific types. There is also a static version that works the same but with static methods: StaticCastingGettersTrait.

Basic Usage

use Joby\Smol\Cast\CastingGettersTrait;
use Joby\Smol\Cast\TypeCastException;

class UserInput
{
    use CastingGettersTrait;
    
    public function __construct(private array $data) {}
    
    protected function getCastableValue(string $name): mixed
    {
        return $this->data[$name] ?? null;
    }
    
    protected function createRequiredException(string $type, string $name): \Throwable
    {
        return new TypeCastException("Required $type property '$name' is null");
    }
    
    protected function createCastException(string $type, string $name, \Throwable $previous): \Throwable
    {
        return new TypeCastException("Cannot cast property '$name' to $type", 0, $previous);
    }
}
$input = new UserInput([
    'user_id' => '123',
    'age' => '25',
    'is_active' => 'yes',
    'email' => 'user@example.com',
]);
$userId = $input->getInt('user_id');      // 123
$age = $input->getInt('age');             // 25
$isActive = $input->getBool('is_active'); // true
$email = $input->getString('email');      // "user@example.com"

Available Methods

The trait provides two variants for each type: get() methods* - Return typed value or null:

  • getInt(string $name, ?int $default = null): ?int
  • getFloat(string $name, ?float $default = null): ?float
  • getBool(string $name, ?bool $default = null): ?bool
  • getString(string $name, ?string $default = null): ?string require() methods* - Return typed value or throw:
  • requireInt(string $name): int
  • requireFloat(string $name): float
  • requireBool(string $name): bool
  • requireString(string $name): string

get*() vs require*()

The key difference is null handling:

// get*() returns null for missing/null values
$optional = $input->getInt('optional_field');  // null
// require*() throws exception for missing/null values
try {
    $required = $input->requireInt('missing_field');
} catch (TypeCastException $e) {
    // "Required int property 'missing_field' is null"
}

Both variants throw exceptions if the value exists but cannot be cast to the target type.

Default Values

All get*() methods accept an optional default value:

// Use default when value is null or missing
$limit = $input->getInt('limit', 10);           // 10 if 'limit' not set
$timeout = $input->getFloat('timeout', 30.0);   // 30.0 if 'timeout' not set
$debug = $input->getBool('debug', false);       // false if 'debug' not set
$theme = $input->getString('theme', 'default'); // 'default' if 'theme' not set
// Default is ignored when value exists (even if zero/false/empty)
$count = $input->getInt('count', 99);  // 0 if 'count' is 0, not 99
$flag = $input->getBool('flag', true); // false if 'flag' is false, not true

Important: Defaults are NOT cast - they must already be the correct type. If a value exists but cannot be cast, an exception is thrown even when a default is provided.

Implementation Requirements

Classes using the trait must implement three abstract methods:

abstract protected function getCastableValue(string $name): mixed;
abstract protected function createRequiredException(string $type, string $name): \Throwable;
abstract protected function createCastException(string $type, string $name, \Throwable $previous): \Throwable;

getCastableValue() - Returns the raw value for the given property name, or null if missing.

createRequiredException() - Creates the exception thrown by require*() methods when a value is null/missing. Receives the requested type name and property name.

createCastException() - Creates the exception thrown when type conversion fails. Receives the requested type name, property name, and the original TypeCastException as previous exception for context.

Custom Exceptions

Implement the exception factory methods to customize error handling:

class ApiResponse
{
    use CastingGettersTrait;
    
    public function __construct(private array $data) {}
    
    protected function getCastableValue(string $name): mixed
    {
        return $this->data[$name] ?? null;
    }
    
    protected function createRequiredException(string $type, string $name): \Throwable
    {
        return new ApiException("Missing required $type field: $name");
    }
    
    protected function createCastException(string $type, string $name, \Throwable $previous): \Throwable
    {
        return new ApiException("Invalid $type value for field '$name'", 0, $previous);
    }
}

This allows each implementing class to:

  • Use domain-specific exception types
  • Customize error messages for better context
  • Include additional debugging information
  • Chain exceptions to preserve the original error details

Usage Patterns

Request/Form Handling

class Request
{
    use CastingGettersTrait;
    
    public function __construct(private array $query, private array $post) {}
    
    protected function getCastableValue(string $key): mixed
    {
        return $this->post[$key] ?? $this->query[$key] ?? null;
    }
}

$request = new Request($_GET, $_POST);

// Required fields throw on missing
$userId = $request->requireInt('user_id');

// Optional fields with defaults
$page = $request->getInt('page', 1);
$perPage = $request->getInt('per_page', 20);
$sortDesc = $request->getBool('sort_desc', false);

Configuration Object

class Config
{
    use CastingGettersTrait;
    
    public function __construct(private array $config) {}
    
    protected function getCastableValue(string $key): mixed
    {
        // Support dot notation for nested config
        $keys = explode('.', $key);
        $value = $this->config;
        
        foreach ($keys as $key) {
            if (!isset($value[$key])) return null;
            $value = $value[$key];
        }
        
        return $value;
    }
}

$config = new Config([
    'database' => [
        'host' => 'localhost',
        'port' => '5432',
    ],
    'cache' => [
        'enabled' => 'yes',
        'ttl' => '3600',
    ],
]);

$dbHost = $config->requireString('database.host');
$dbPort = $config->getInt('database.port', 5432);
$cacheEnabled = $config->getBool('cache.enabled', false);
$cacheTtl = $config->getInt('cache.ttl', 300);

API Response Wrapper

class ApiResponse
{
    use CastingGettersTrait;
    
    public function __construct(private array $data) {}
    
    protected function getCastableValue(string $key): mixed
    {
        return $this->data[$key] ?? null;
    }
    
    public function isSuccess(): bool
    {
        return $this->requireBool('success');
    }
    
    public function getErrorMessage(): ?string
    {
        return $this->getString('error');
    }
}

$response = new ApiResponse(json_decode($json, true));

if ($response->isSuccess()) {
    $userId = $response->requireInt('user_id');
    $username = $response->requireString('username');
} else {
    $error = $response->getErrorMessage() ?? 'Unknown error';
}

Database Row Object

class UserRow
{
    use CastingGettersTrait;
    
    public function __construct(private array $row) {}
    
    protected function getCastableValue(string $key): mixed
    {
        return $this->row[$key] ?? null;
    }
}

// Many DB drivers return all values as strings
$row = $pdo->fetch(PDO::FETCH_ASSOC);
$user = new UserRow($row);

$id = $user->requireInt('id');              // "123" → 123
$balance = $user->getFloat('balance');      // "19.99" → 19.99
$isActive = $user->getBool('is_active');    // "1" → true
$email = $user->getString('email');         // string

Implementation Requirements

Classes using the trait must implement:

abstract protected function getCastableValue(string $key): mixed;

This method should:

  • Return the raw value for the given property name
  • Return null for missing properties
  • Return the actual stored value for properties that exist (even if that value is null)

The trait handles all type conversion and error handling.

Requirements

PHP 8.1+

License

MIT License - See LICENSE file for details.

joby/smol-cast 适用场景与选型建议

joby/smol-cast 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 504 次下载、GitHub Stars 达 0, 最近一次更新时间为 2026 年 01 月 25 日, 在 PHP 生态内属于活跃度较高的组件。

我们在过去多个企业项目中使用过 joby/smol-cast 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 joby/smol-cast 我们能提供哪些服务?
定制开发 / 二次开发

基于 joby/smol-cast 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 504
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 25
  • 依赖项目数: 4
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-01-25