spatie/better-types
Reflection-powered type checking for PHP: verify whether a ReflectionType or method signature accepts given arguments (including unions/nullables and named params). Useful for dispatching/overload-like method selection and safer dynamic calls.
Installation:
composer require spatie/better-types
No additional configuration is required—just autoload the package.
First Use Case: Check if a value matches a type hint in a method or property:
use Spatie\BetterTypes\BetterTypes;
$value = 123;
$reflectionMethod = new ReflectionMethod(SomeClass::class, 'someMethod');
if (BetterTypes::accepts($reflectionMethod, $value)) {
// Value matches the type hint
}
Key Classes:
BetterTypes – Main facade for type checks.Type – Represents a type (e.g., Type::fromReflectionType($reflectionType)).UnionType – Handles union types (e.g., Type::union([Type::string(), Type::int()])).accepts(), is(), etc.Type Validation in Controllers/Requests: Validate input against method type hints dynamically:
public function store(Request $request) {
$reflectionMethod = new ReflectionMethod($this, 'store');
$input = $request->input('data');
if (!BetterTypes::accepts($reflectionMethod, $input)) {
throw new \InvalidArgumentException('Invalid input type');
}
}
Union Type Handling: Check if a value matches any type in a union:
$unionType = Type::union([Type::string(), Type::array()]);
if ($unionType->accepts($value)) {
// Valid for string or array
}
Named Types (PHP 8.2+):
Validate custom types (e.g., DateTimeInterface):
$type = Type::fromReflectionType(new ReflectionNamedType(DateTimeInterface::class));
if ($type->accepts($value)) {
// Valid DateTimeInterface
}
Property Validation: Check if a class property’s type hint matches a value:
$property = new ReflectionProperty(SomeClass::class, 'propertyName');
if (BetterTypes::accepts($property, $value)) {
// Type-compatible
}
Laravel Request Validation: Combine with Laravel’s validation to enforce type hints:
public function update(Request $request) {
$reflectionMethod = new ReflectionMethod($this, 'update');
$validated = $request->validate([
'data' => ['required', fn($attr, $value) =>
BetterTypes::accepts($reflectionMethod, $value) ?: 'Invalid type'
],
]);
}
API Responses: Use to ensure response data matches expected types before serialization:
$responseData = $this->service->process();
$expectedType = Type::array()->of(Type::string());
if (!$expectedType->accepts($responseData)) {
throw new \RuntimeException('Response type mismatch');
}
Testing: Assert type compatibility in PHPUnit:
$this->assertTrue(BetterTypes::accepts($reflectionMethod, $validValue));
$this->assertFalse(BetterTypes::accepts($reflectionMethod, $invalidValue));
Reflection Limitations:
ReflectionMethod::newInstance() with null for private methods.eval), reflection may not work. Cache Reflection* objects if possible.Union Type Edge Cases:
Type::union([]) will always reject values. Validate the union array isn’t empty.Type::nullable($type) explicitly for ?Type hints.PHP Version Quirks:
DateTimeInterface) may not resolve correctly. Use ReflectionNamedType explicitly.array<int, string>) aren’t supported. Fall back to Type::array()->of(Type::int()).Performance:
Reflection* objects or Type instances:
private $cachedTypes = [];
$type = $this->cachedTypes[$methodName] ??= Type::fromReflectionType($reflectionMethod->getType());
Inspect Types:
Use Type::describe() to debug complex types:
$type = Type::fromReflectionType($reflectionMethod->getType());
dump($type->describe()); // e.g., "array<int, string>"
Common Errors:
DateTime vs. DateTimeInterface).Extension Points:
Spatie\BetterTypes\Type to handle domain-specific types:
class CustomType extends Type {
public function accepts($value): bool {
return $value instanceof CustomClass;
}
}
app()->bind(Type::class, fn() => new CustomType());
strict_types=1 for stricter type checks (recommended).Combine with spatie/laravel-data:
Use BetterTypes to validate Data objects before/after serialization:
$data = new UserData($request->all());
$type = Type::fromReflectionType(new ReflectionNamedType(UserData::class));
if (!$type->accepts($data)) {
throw new \InvalidArgumentException('Data type mismatch');
}
Type-Safe Collections:
Validate collection items using Type::array()->of($itemType):
$items = [1, 2, 3];
$type = Type::array()->of(Type::int());
if ($type->accepts($items)) {
// All items are integers
}
Dynamic Type Generation: Generate types from strings for runtime validation:
$type = Type::fromString('array<int, string>');
if ($type->accepts($value)) {
// Valid array<int, string>
}
How can I help you explore Laravel packages today?