typhoon/type
Typhoon Type provides an object abstraction over PHP’s modern type system for building tools that understand complex types. Define, print (stringify), and work with array shapes, object types, non-empty lists, and more in a consistent API.
Installation:
composer require typhoon/type
Add to composer.json under require-dev if using for type checking/testing.
First Use Case: Define a type for validation or runtime checks:
use function Typhoon\Type\{stringT, arrayT, objectT, stringify};
$type = arrayT(value: objectT(User::class));
echo stringify($type); // "array<User>"
Key Entry Points:
Typhoon\Type\Type (base interface)stringT(), arrayT(), objectT())stringify() for debuggingTyphoon\Type\Visitor for custom operationsWorkflow: Build complex types hierarchically for validation or runtime checks.
// Define a nested type for API request validation
$requestType = arrayShapeT([
'user' => objectT(User::class),
'metadata' => optional(arrayT(stringT, intT)),
]);
// Use in a validator
$validator = new Validator($requestType);
$isValid = $validator->validate($requestData);
Integration Tip:
arrayShapeT()/objectShapeT() for structured data (e.g., DTOs, API payloads).optional(), nonEmptyListT(), or unionT() for flexible schemas.Workflow: Leverage Typhoon\Type\Is for dynamic type validation.
use Typhoon\Type\Is;
$is = new Is();
$result = $is->test($value, stringT()); // bool
Laravel Integration:
public function rules()
{
return [
'data' => ['required', function ($attribute, $value, $fail) {
$is = new Is();
if (!$is->test($value, $this->getExpectedType())) {
$fail('Invalid data structure.');
}
}],
];
}
Workflow: Map between types and data structures (e.g., JSON ↔ PHP objects).
use Typhoon\Type\{arrayShapeT, objectT, nonEmptyListT};
$type = arrayShapeT([
'users' => nonEmptyListT(objectT(User::class)),
]);
$mapper = new Mapper();
$users = $mapper->map($jsonData, $type); // Decodes + validates
Laravel Use Case:
public function handle(Request $request)
{
$type = arrayShapeT(['query' => stringT()]);
$parsed = (new Mapper())->map($request->all(), $type);
// $parsed['query'] is guaranteed to be string
}
Workflow: Extend Typhoon\Type\Visitor to add domain logic (e.g., generate OpenAPI schemas).
class OpenApiVisitor implements Visitor
{
public function visitStringT(StringT $type): string
{
return 'string';
}
public function visitArrayShapeT(ArrayShapeT $type): string
{
$properties = [];
foreach ($type->shape as $key => $valueType) {
$properties[$key] = $this->visit($valueType);
}
return json_encode(['type' => 'object', 'properties' => $properties]);
}
// ... implement other visit methods
}
Laravel Integration:
$visitor = new OpenApiVisitor();
$schema = $visitor->visit($requestType);
Workflow: Use types to enforce constructor signatures in Laravel’s container.
// Define a type for a service constructor
$serviceType = objectT(Service::class, [stringT(), intT()]);
// In a factory or resolver
$container->when(Service::class)
->needsConstructor()
->give(function ($c) {
$args = $c->make([stringT(), intT()]); // Enforced types
return new Service(...$args);
});
Invariant Generics (0.8.0+):
arrayT(K, V)) are invariant by default. Avoid unsafe casts:
// ❌ Unsafe: arrayT(intT, stringT) is NOT a subtype of arrayT(intT, mixedT)
$is->test($value, arrayT(intT, mixedT)); // May fail unexpectedly
unionT() or intersectionT() for broader compatibility.Null Handling:
nullT is distinct from optional(stringT). Explicitly handle null cases:
$type = unionT(nullT, stringT()); // Accepts both null and string
PHPDoc vs. Runtime Types:
array-key) map to arrayKeyT() but may behave differently at runtime. Test edge cases:
$is->test('key', arrayKeyT()); // true
$is->test(123, arrayKeyT()); // false (numeric keys are allowed in PHP arrays)
Performance:
arrayShapeT) can slow down validation. Cache Type instances:
static $cachedType = arrayShapeT([...]); // Reuse instead of recreating
Use stringify():
echo stringify($type); // Human-readable output
Visitor Debugging:
DebugVisitor to trace type traversal:
class DebugVisitor implements Visitor {
public function visit($type): void {
echo "Visiting: " . get_class($type) . "\n";
// ... delegate to other visit methods
}
}
Laravel Logging:
$is = new Is();
if (!$is->test($value, $expectedType)) {
Log::error("Type mismatch. Expected: " . stringify($expectedType));
}
Custom Type Constructors:
dateT()):
function dateT(): DateT {
return new DateT();
}
Visitor Patterns:
Integration with Laravel Packages:
$searchType = arrayShapeT(['query' => stringT(), 'page' => intT()]);
$middleware->validate($request, $apiType);
Testing:
Typhoon\Type\Is in PHPUnit assertions:
$this->assertTrue((new Is())->test($value, stringT()));
How can I help you explore Laravel packages today?